2016-04-13 07:08:22 -07:00
// Copyright 2016 The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
2015-06-04 09:07:57 -07:00
package v1
import (
2017-10-24 21:21:42 -07:00
"context"
2015-06-04 09:07:57 -07:00
"encoding/json"
"fmt"
2022-04-27 02:24:36 -07:00
"io"
2018-02-08 09:28:55 -08:00
"math"
2015-06-04 09:07:57 -07:00
"net/http"
"net/http/httptest"
"net/url"
2018-11-15 05:22:16 -08:00
"os"
2015-06-04 09:07:57 -07:00
"reflect"
2020-07-31 08:03:02 -07:00
"runtime"
2019-12-09 13:36:38 -08:00
"sort"
2017-11-10 16:53:48 -08:00
"strings"
2015-06-04 09:07:57 -07:00
"testing"
"time"
2022-02-10 06:17:05 -08:00
"github.com/prometheus/prometheus/util/stats"
2021-06-11 09:17:59 -07:00
"github.com/go-kit/log"
2020-10-22 02:00:08 -07:00
"github.com/pkg/errors"
2018-09-07 14:26:04 -07:00
"github.com/prometheus/client_golang/prometheus"
2018-06-16 10:26:37 -07:00
config_util "github.com/prometheus/common/config"
2015-08-20 08:18:46 -07:00
"github.com/prometheus/common/model"
2018-06-16 10:26:37 -07:00
"github.com/prometheus/common/promlog"
2015-09-24 08:07:11 -07:00
"github.com/prometheus/common/route"
2020-10-29 02:43:23 -07:00
"github.com/stretchr/testify/require"
2015-06-04 09:07:57 -07:00
2017-05-11 08:09:24 -07:00
"github.com/prometheus/prometheus/config"
2021-11-08 06:23:17 -08:00
"github.com/prometheus/prometheus/model/exemplar"
"github.com/prometheus/prometheus/model/labels"
"github.com/prometheus/prometheus/model/textparse"
"github.com/prometheus/prometheus/model/timestamp"
2017-10-23 13:28:17 -07:00
"github.com/prometheus/prometheus/prompb"
2015-06-04 09:07:57 -07:00
"github.com/prometheus/prometheus/promql"
2020-02-03 10:23:07 -08:00
"github.com/prometheus/prometheus/promql/parser"
2018-03-25 09:50:34 -07:00
"github.com/prometheus/prometheus/rules"
2018-02-01 01:55:07 -08:00
"github.com/prometheus/prometheus/scrape"
2018-05-08 01:48:13 -07:00
"github.com/prometheus/prometheus/storage"
2017-10-23 13:28:17 -07:00
"github.com/prometheus/prometheus/storage/remote"
2019-11-18 11:53:33 -08:00
"github.com/prometheus/prometheus/tsdb"
2019-08-08 18:35:39 -07:00
"github.com/prometheus/prometheus/util/teststorage"
2015-06-04 09:07:57 -07:00
)
2019-12-04 11:33:01 -08:00
// 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
}
2020-01-29 03:13:18 -08:00
func ( s * testMetaStore ) SizeMetadata ( ) int { return 0 }
func ( s * testMetaStore ) LengthMetadata ( ) int { return 0 }
2019-12-04 11:33:01 -08:00
// testTargetRetriever represents a list of targets to scrape.
// It is used to represent targets as part of test cases.
2019-12-04 03:08:21 -08:00
type testTargetRetriever struct {
activeTargets map [ string ] [ ] * scrape . Target
droppedTargets map [ string ] [ ] * scrape . Target
}
type testTargetParams struct {
Identifier string
Labels [ ] labels . Label
DiscoveredLabels [ ] labels . Label
Params url . Values
Reports [ ] * testReport
Active bool
}
type testReport struct {
Start time . Time
Duration time . Duration
Error error
}
func newTestTargetRetriever ( targetsInfo [ ] * testTargetParams ) * testTargetRetriever {
var activeTargets map [ string ] [ ] * scrape . Target
var droppedTargets map [ string ] [ ] * scrape . Target
activeTargets = make ( map [ string ] [ ] * scrape . Target )
droppedTargets = make ( map [ string ] [ ] * scrape . Target )
for _ , t := range targetsInfo {
nt := scrape . NewTarget ( t . Labels , t . DiscoveredLabels , t . Params )
for _ , r := range t . Reports {
nt . Report ( r . Start , r . Duration , r . Error )
}
if t . Active {
activeTargets [ t . Identifier ] = [ ] * scrape . Target { nt }
} else {
droppedTargets [ t . Identifier ] = [ ] * scrape . Target { nt }
}
}
return & testTargetRetriever {
activeTargets : activeTargets ,
droppedTargets : droppedTargets ,
}
}
2016-12-02 04:31:43 -08:00
2021-10-22 01:06:44 -07:00
var scrapeStart = time . Now ( ) . Add ( - 11 * time . Second )
2019-11-11 13:42:24 -08:00
2018-09-26 02:20:56 -07:00
func ( t testTargetRetriever ) TargetsActive ( ) map [ string ] [ ] * scrape . Target {
2019-12-04 03:08:21 -08:00
return t . activeTargets
2018-02-21 09:26:18 -08:00
}
2019-12-04 03:08:21 -08:00
2018-09-26 02:20:56 -07:00
func ( t testTargetRetriever ) TargetsDropped ( ) map [ string ] [ ] * scrape . Target {
2019-12-04 03:08:21 -08:00
return t . droppedTargets
2016-12-02 04:31:43 -08:00
}
2019-12-10 06:56:16 -08:00
func ( t * testTargetRetriever ) SetMetadataStoreForTargets ( identifier string , metadata scrape . MetricMetadataStore ) error {
2019-12-04 11:33:01 -08:00
targets , ok := t . activeTargets [ identifier ]
if ! ok {
return errors . New ( "targets not found" )
}
for _ , at := range targets {
at . SetMetadataStore ( metadata )
}
return nil
}
2019-12-10 06:56:16 -08:00
func ( t * testTargetRetriever ) ResetMetadataStore ( ) {
for _ , at := range t . activeTargets {
for _ , tt := range at {
tt . SetMetadataStore ( & testMetaStore { } )
}
}
}
2020-04-16 01:30:47 -07:00
func ( t * testTargetRetriever ) toFactory ( ) func ( context . Context ) TargetRetriever {
2020-05-18 11:02:32 -07:00
return func ( context . Context ) TargetRetriever { return t }
2020-04-16 01:30:47 -07:00
}
2018-02-21 01:00:07 -08:00
type testAlertmanagerRetriever struct { }
2017-01-13 01:20:11 -08:00
2018-02-21 01:00:07 -08:00
func ( t testAlertmanagerRetriever ) Alertmanagers ( ) [ ] * url . URL {
return [ ] * url . URL {
{
Scheme : "http" ,
Host : "alertmanager.example.com:8080" ,
Path : "/api/v1/alerts" ,
} ,
}
}
func ( t testAlertmanagerRetriever ) DroppedAlertmanagers ( ) [ ] * url . URL {
return [ ] * url . URL {
{
Scheme : "http" ,
Host : "dropped.alertmanager.example.com:8080" ,
Path : "/api/v1/alerts" ,
} ,
}
2016-12-02 04:31:43 -08:00
}
2020-05-18 11:02:32 -07:00
func ( t testAlertmanagerRetriever ) toFactory ( ) func ( context . Context ) AlertmanagerRetriever {
return func ( context . Context ) AlertmanagerRetriever { return t }
}
2018-06-27 00:15:17 -07:00
type rulesRetrieverMock struct {
testing * testing . T
2018-03-25 09:50:34 -07:00
}
2018-06-27 00:15:17 -07:00
func ( m rulesRetrieverMock ) AlertingRules ( ) [ ] * rules . AlertingRule {
2020-02-03 10:23:07 -08:00
expr1 , err := parser . ParseExpr ( ` absent(test_metric3) != 1 ` )
2018-03-25 09:50:34 -07:00
if err != nil {
2018-06-27 00:15:17 -07:00
m . testing . Fatalf ( "unable to parse alert expression: %s" , err )
2018-03-25 09:50:34 -07:00
}
2020-02-03 10:23:07 -08:00
expr2 , err := parser . ParseExpr ( ` up == 1 ` )
2018-03-25 09:50:34 -07:00
if err != nil {
2018-06-27 00:15:17 -07:00
m . testing . Fatalf ( "Unable to parse alert expression: %s" , err )
2018-03-25 09:50:34 -07:00
}
rule1 := rules . NewAlertingRule (
"test_metric3" ,
expr1 ,
time . Second ,
labels . Labels { } ,
labels . Labels { } ,
2019-04-15 09:52:58 -07:00
labels . Labels { } ,
2021-05-30 20:56:01 -07:00
"" ,
2018-08-02 03:18:24 -07:00
true ,
2018-03-25 09:50:34 -07:00
log . NewNopLogger ( ) ,
)
rule2 := rules . NewAlertingRule (
"test_metric4" ,
expr2 ,
time . Second ,
labels . Labels { } ,
labels . Labels { } ,
2019-04-15 09:52:58 -07:00
labels . Labels { } ,
2021-05-30 20:56:01 -07:00
"" ,
2018-08-02 03:18:24 -07:00
true ,
2018-03-25 09:50:34 -07:00
log . NewNopLogger ( ) ,
)
var r [ ] * rules . AlertingRule
r = append ( r , rule1 )
r = append ( r , rule2 )
return r
}
2018-06-27 00:15:17 -07:00
func ( m rulesRetrieverMock ) RuleGroups ( ) [ ] * rules . Group {
var ar rulesRetrieverMock
2018-03-25 09:50:34 -07:00
arules := ar . AlertingRules ( )
2019-08-08 18:35:39 -07:00
storage := teststorage . New ( m . testing )
2018-03-25 09:50:34 -07:00
defer storage . Close ( )
2018-10-02 04:59:19 -07:00
engineOpts := promql . EngineOpts {
2020-01-28 12:38:49 -08:00
Logger : nil ,
Reg : nil ,
MaxSamples : 10 ,
Timeout : 100 * time . Second ,
2018-10-02 04:59:19 -07:00
}
engine := promql . NewEngine ( engineOpts )
2018-03-25 09:50:34 -07:00
opts := & rules . ManagerOptions {
QueryFunc : rules . EngineQueryFunc ( engine , storage ) ,
Appendable : storage ,
Context : context . Background ( ) ,
Logger : log . NewNopLogger ( ) ,
}
var r [ ] rules . Rule
for _ , alertrule := range arules {
r = append ( r , alertrule )
}
2020-02-03 10:23:07 -08:00
recordingExpr , err := parser . ParseExpr ( ` vector(1) ` )
2018-06-27 00:15:17 -07:00
if err != nil {
m . testing . Fatalf ( "unable to parse alert expression: %s" , err )
}
recordingRule := rules . NewRecordingRule ( "recording-rule-1" , recordingExpr , labels . Labels { } )
r = append ( r , recordingRule )
2020-02-12 07:22:18 -08:00
group := rules . NewGroup ( rules . GroupOptions {
Name : "grp" ,
File : "/path/to/file" ,
Interval : time . Second ,
Rules : r ,
ShouldRestore : false ,
Opts : opts ,
} )
2018-03-25 09:50:34 -07:00
return [ ] * rules . Group { group }
}
2020-05-18 11:02:32 -07:00
func ( m rulesRetrieverMock ) toFactory ( ) func ( context . Context ) RulesRetriever {
return func ( context . Context ) RulesRetriever { return m }
}
2017-05-11 08:09:24 -07:00
var samplePrometheusCfg = config . Config {
GlobalConfig : config . GlobalConfig { } ,
AlertingConfig : config . AlertingConfig { } ,
RuleFiles : [ ] string { } ,
ScrapeConfigs : [ ] * config . ScrapeConfig { } ,
RemoteWriteConfigs : [ ] * config . RemoteWriteConfig { } ,
RemoteReadConfigs : [ ] * config . RemoteReadConfig { } ,
}
api: Added v1/status/flags endpoint. (#3864)
Endpoint URL: /api/v1/status/flags
Example Output:
```json
{
"status": "success",
"data": {
"alertmanager.notification-queue-capacity": "10000",
"alertmanager.timeout": "10s",
"completion-bash": "false",
"completion-script-bash": "false",
"completion-script-zsh": "false",
"config.file": "my_cool_prometheus.yaml",
"help": "false",
"help-long": "false",
"help-man": "false",
"log.level": "info",
"query.lookback-delta": "5m",
"query.max-concurrency": "20",
"query.timeout": "2m",
"storage.tsdb.max-block-duration": "36h",
"storage.tsdb.min-block-duration": "2h",
"storage.tsdb.no-lockfile": "false",
"storage.tsdb.path": "data/",
"storage.tsdb.retention": "15d",
"version": "false",
"web.console.libraries": "console_libraries",
"web.console.templates": "consoles",
"web.enable-admin-api": "false",
"web.enable-lifecycle": "false",
"web.external-url": "",
"web.listen-address": "0.0.0.0:9090",
"web.max-connections": "512",
"web.read-timeout": "5m",
"web.route-prefix": "/",
"web.user-assets": ""
}
}
```
Signed-off-by: Bartek Plotka <bwplotka@gmail.com>
2018-02-21 00:49:02 -08:00
var sampleFlagMap = map [ string ] string {
"flag1" : "value1" ,
"flag2" : "value2" ,
}
2015-06-04 09:07:57 -07:00
func TestEndpoints ( t * testing . T ) {
suite , err := promql . NewTest ( t , `
load 1 m
test_metric1 { foo = "bar" } 0 + 100 x100
test_metric1 { foo = "boo" } 1 + 0x100
test_metric2 { foo = "boo" } 1 + 0x100
2020-08-28 16:21:39 -07:00
test_metric3 { foo = "bar" , dup = "1" } 1 + 0x100
test_metric3 { foo = "boo" , dup = "1" } 1 + 0x100
test_metric4 { foo = "bar" , dup = "1" } 1 + 0x100
test_metric4 { foo = "boo" , dup = "1" } 1 + 0x100
test_metric4 { foo = "boo" } 1 + 0x100
2015-06-04 09:07:57 -07:00
` )
2021-03-16 02:47:45 -07:00
start := time . Unix ( 0 , 0 )
exemplars := [ ] exemplar . QueryResult {
{
SeriesLabels : labels . FromStrings ( "__name__" , "test_metric3" , "foo" , "boo" , "dup" , "1" ) ,
Exemplars : [ ] exemplar . Exemplar {
{
Labels : labels . FromStrings ( "id" , "abc" ) ,
Value : 10 ,
Ts : timestamp . FromTime ( start . Add ( 2 * time . Second ) ) ,
} ,
} ,
} ,
{
SeriesLabels : labels . FromStrings ( "__name__" , "test_metric4" , "foo" , "bar" , "dup" , "1" ) ,
Exemplars : [ ] exemplar . Exemplar {
{
Labels : labels . FromStrings ( "id" , "lul" ) ,
Value : 10 ,
Ts : timestamp . FromTime ( start . Add ( 4 * time . Second ) ) ,
} ,
} ,
} ,
{
SeriesLabels : labels . FromStrings ( "__name__" , "test_metric3" , "foo" , "boo" , "dup" , "1" ) ,
Exemplars : [ ] exemplar . Exemplar {
{
Labels : labels . FromStrings ( "id" , "abc2" ) ,
Value : 10 ,
Ts : timestamp . FromTime ( start . Add ( 4053 * time . Millisecond ) ) ,
} ,
} ,
} ,
{
SeriesLabels : labels . FromStrings ( "__name__" , "test_metric4" , "foo" , "bar" , "dup" , "1" ) ,
Exemplars : [ ] exemplar . Exemplar {
{
Labels : labels . FromStrings ( "id" , "lul2" ) ,
Value : 10 ,
Ts : timestamp . FromTime ( start . Add ( 4153 * time . Millisecond ) ) ,
} ,
} ,
} ,
}
for _ , ed := range exemplars {
suite . ExemplarStorage ( ) . AppendExemplar ( 0 , ed . SeriesLabels , ed . Exemplars [ 0 ] )
require . NoError ( t , err , "failed to add exemplar: %+v" , ed . Exemplars [ 0 ] )
}
2020-10-29 02:43:23 -07:00
require . NoError ( t , err )
2015-06-04 09:07:57 -07:00
defer suite . Close ( )
2020-10-29 02:43:23 -07:00
require . NoError ( t , suite . Run ( ) )
2015-06-04 09:07:57 -07:00
2016-12-30 01:43:44 -08:00
now := time . Now ( )
2016-12-02 04:31:43 -08:00
2018-06-27 00:15:17 -07:00
t . Run ( "local" , func ( t * testing . T ) {
var algr rulesRetrieverMock
algr . testing = t
2018-03-25 09:50:34 -07:00
algr . AlertingRules ( )
algr . RuleGroups ( )
2019-12-04 11:33:01 -08:00
testTargetRetriever := setupTestTargetRetriever ( t )
2019-12-04 03:08:21 -08:00
2018-06-16 10:26:37 -07:00
api := & API {
Queryable : suite . Storage ( ) ,
QueryEngine : suite . QueryEngine ( ) ,
2021-03-16 02:47:45 -07:00
ExemplarQueryable : suite . ExemplarQueryable ( ) ,
2020-04-16 01:30:47 -07:00
targetRetriever : testTargetRetriever . toFactory ( ) ,
2020-05-18 11:02:32 -07:00
alertmanagerRetriever : testAlertmanagerRetriever { } . toFactory ( ) ,
2018-11-19 02:21:14 -08:00
flagsMap : sampleFlagMap ,
2018-10-16 00:41:45 -07:00
now : func ( ) time . Time { return now } ,
config : func ( ) config . Config { return samplePrometheusCfg } ,
ready : func ( f http . HandlerFunc ) http . HandlerFunc { return f } ,
2020-05-18 11:02:32 -07:00
rulesRetriever : algr . toFactory ( ) ,
2018-06-16 10:26:37 -07:00
}
2021-03-16 02:47:45 -07:00
testEndpoints ( t , api , testTargetRetriever , suite . ExemplarStorage ( ) , true )
2018-06-16 10:26:37 -07:00
} )
2017-01-13 01:20:11 -08:00
2018-06-16 10:26:37 -07:00
// Run all the API tests against a API that is wired to forward queries via
// the remote read client to a test server, which in turn sends them to the
2018-06-18 09:32:44 -07:00
// data from the test suite.
2018-06-16 10:26:37 -07:00
t . Run ( "remote" , func ( t * testing . T ) {
server := setupRemote ( suite . Storage ( ) )
defer server . Close ( )
u , err := url . Parse ( server . URL )
2020-10-29 02:43:23 -07:00
require . NoError ( t , err )
2018-06-16 10:26:37 -07:00
al := promlog . AllowedLevel { }
2020-10-29 02:43:23 -07:00
require . NoError ( t , al . Set ( "debug" ) )
2019-07-29 10:00:30 -07:00
2018-11-23 05:22:40 -08:00
af := promlog . AllowedFormat { }
2020-10-29 02:43:23 -07:00
require . NoError ( t , af . Set ( "logfmt" ) )
2019-07-29 10:00:30 -07:00
2018-11-23 05:22:40 -08:00
promlogConfig := promlog . Config {
Level : & al ,
Format : & af ,
}
2021-12-08 14:14:50 -08:00
dbDir := t . TempDir ( )
2018-09-07 14:26:04 -07:00
2020-11-19 07:23:03 -08:00
remote := remote . NewStorage ( promlog . New ( & promlogConfig ) , prometheus . DefaultRegisterer , func ( ) ( int64 , error ) {
return 0 , nil
} , dbDir , 1 * time . Second , nil )
2018-06-16 10:26:37 -07:00
err = remote . ApplyConfig ( & config . Config {
RemoteReadConfigs : [ ] * config . RemoteReadConfig {
{
URL : & config_util . URL { URL : u } ,
RemoteTimeout : model . Duration ( 1 * time . Second ) ,
ReadRecent : true ,
} ,
} ,
} )
2020-10-29 02:43:23 -07:00
require . NoError ( t , err )
2018-06-16 10:26:37 -07:00
2018-06-27 00:15:17 -07:00
var algr rulesRetrieverMock
algr . testing = t
2018-03-25 09:50:34 -07:00
algr . AlertingRules ( )
algr . RuleGroups ( )
2019-12-04 11:33:01 -08:00
testTargetRetriever := setupTestTargetRetriever ( t )
2019-12-04 03:08:21 -08:00
2018-06-16 10:26:37 -07:00
api := & API {
Queryable : remote ,
QueryEngine : suite . QueryEngine ( ) ,
2021-03-16 02:47:45 -07:00
ExemplarQueryable : suite . ExemplarQueryable ( ) ,
2020-04-16 01:30:47 -07:00
targetRetriever : testTargetRetriever . toFactory ( ) ,
2020-05-18 11:02:32 -07:00
alertmanagerRetriever : testAlertmanagerRetriever { } . toFactory ( ) ,
2018-11-19 02:21:14 -08:00
flagsMap : sampleFlagMap ,
2018-10-16 00:41:45 -07:00
now : func ( ) time . Time { return now } ,
config : func ( ) config . Config { return samplePrometheusCfg } ,
ready : func ( f http . HandlerFunc ) http . HandlerFunc { return f } ,
2020-05-18 11:02:32 -07:00
rulesRetriever : algr . toFactory ( ) ,
2018-06-16 10:26:37 -07:00
}
2021-03-16 02:47:45 -07:00
testEndpoints ( t , api , testTargetRetriever , suite . ExemplarStorage ( ) , false )
2018-06-16 10:26:37 -07:00
} )
2018-11-19 02:21:14 -08:00
}
func TestLabelNames ( t * testing . T ) {
// TestEndpoints doesn't have enough label names to test api.labelNames
// endpoint properly. Hence we test it separately.
suite , err := promql . NewTest ( t , `
load 1 m
test_metric1 { foo1 = "bar" , baz = "abc" } 0 + 100 x100
test_metric1 { foo2 = "boo" } 1 + 0x100
test_metric2 { foo = "boo" } 1 + 0x100
test_metric2 { foo = "boo" , xyz = "qwerty" } 1 + 0x100
2021-07-20 05:38:08 -07:00
test_metric2 { foo = "baz" , abc = "qwerty" } 1 + 0x100
2018-11-19 02:21:14 -08:00
` )
2020-10-29 02:43:23 -07:00
require . NoError ( t , err )
2018-11-19 02:21:14 -08:00
defer suite . Close ( )
2020-10-29 02:43:23 -07:00
require . NoError ( t , suite . Run ( ) )
2018-11-19 02:21:14 -08:00
api := & API {
Queryable : suite . Storage ( ) ,
}
2021-07-20 05:38:08 -07:00
request := func ( method string , matchers ... string ) ( * http . Request , error ) {
u , err := url . Parse ( "http://example.com" )
require . NoError ( t , err )
q := u . Query ( )
for _ , matcher := range matchers {
q . Add ( "match[]" , matcher )
}
u . RawQuery = q . Encode ( )
r , err := http . NewRequest ( method , u . String ( ) , nil )
if method == http . MethodPost {
2018-11-19 02:21:14 -08:00
r . Header . Set ( "Content-Type" , "application/x-www-form-urlencoded" )
}
2021-07-20 05:38:08 -07:00
return r , err
2018-11-19 02:21:14 -08:00
}
2021-07-20 05:38:08 -07:00
for _ , tc := range [ ] struct {
name string
matchers [ ] string
expected [ ] string
} {
{
name : "no matchers" ,
expected : [ ] string { "__name__" , "abc" , "baz" , "foo" , "foo1" , "foo2" , "xyz" } ,
} ,
{
name : "non empty label matcher" ,
matchers : [ ] string { ` { foo=~".+"} ` } ,
expected : [ ] string { "__name__" , "abc" , "foo" , "xyz" } ,
} ,
{
name : "exact label matcher" ,
matchers : [ ] string { ` { foo="boo"} ` } ,
expected : [ ] string { "__name__" , "foo" , "xyz" } ,
} ,
{
name : "two matchers" ,
matchers : [ ] string { ` { foo="boo"} ` , ` { foo="baz"} ` } ,
expected : [ ] string { "__name__" , "abc" , "foo" , "xyz" } ,
} ,
} {
t . Run ( tc . name , func ( t * testing . T ) {
for _ , method := range [ ] string { http . MethodGet , http . MethodPost } {
ctx := context . Background ( )
req , err := request ( method , tc . matchers ... )
require . NoError ( t , err )
res := api . labelNames ( req . WithContext ( ctx ) )
assertAPIError ( t , res . err , "" )
assertAPIResponse ( t , res . data , tc . expected )
}
} )
2018-11-19 02:21:14 -08:00
}
2018-06-16 10:26:37 -07:00
}
2022-02-10 06:17:05 -08:00
type testStats struct {
Custom string ` json:"custom" `
}
func ( testStats ) Builtin ( ) ( _ stats . BuiltinStats ) {
return
}
2022-02-01 18:07:23 -08:00
func TestStats ( t * testing . T ) {
suite , err := promql . NewTest ( t , ` ` )
require . NoError ( t , err )
defer suite . Close ( )
require . NoError ( t , suite . Run ( ) )
api := & API {
Queryable : suite . Storage ( ) ,
QueryEngine : suite . QueryEngine ( ) ,
now : func ( ) time . Time {
return time . Unix ( 123 , 0 )
} ,
}
2022-02-10 06:17:05 -08:00
request := func ( method , param string ) ( * http . Request , error ) {
2022-02-01 18:07:23 -08:00
u , err := url . Parse ( "http://example.com" )
require . NoError ( t , err )
q := u . Query ( )
q . Add ( "stats" , param )
q . Add ( "query" , "up" )
q . Add ( "start" , "0" )
q . Add ( "end" , "100" )
q . Add ( "step" , "10" )
u . RawQuery = q . Encode ( )
r , err := http . NewRequest ( method , u . String ( ) , nil )
if method == http . MethodPost {
r . Header . Set ( "Content-Type" , "application/x-www-form-urlencoded" )
}
return r , err
}
for _ , tc := range [ ] struct {
name string
2022-02-10 06:17:05 -08:00
renderer StatsRenderer
2022-02-01 18:07:23 -08:00
param string
expected func ( * testing . T , interface { } )
} {
{
name : "stats is blank" ,
param : "" ,
expected : func ( t * testing . T , i interface { } ) {
require . IsType ( t , i , & queryData { } )
qd := i . ( * queryData )
require . Nil ( t , qd . Stats )
} ,
} ,
{
name : "stats is true" ,
param : "true" ,
expected : func ( t * testing . T , i interface { } ) {
require . IsType ( t , i , & queryData { } )
qd := i . ( * queryData )
require . NotNil ( t , qd . Stats )
2022-02-10 06:17:05 -08:00
qs := qd . Stats . Builtin ( )
2022-02-01 18:07:23 -08:00
require . NotNil ( t , qs . Timings )
require . Greater ( t , qs . Timings . EvalTotalTime , float64 ( 0 ) )
require . NotNil ( t , qs . Samples )
require . NotNil ( t , qs . Samples . TotalQueryableSamples )
require . Nil ( t , qs . Samples . TotalQueryableSamplesPerStep )
} ,
} ,
{
name : "stats is all" ,
param : "all" ,
expected : func ( t * testing . T , i interface { } ) {
require . IsType ( t , i , & queryData { } )
qd := i . ( * queryData )
require . NotNil ( t , qd . Stats )
2022-02-10 06:17:05 -08:00
qs := qd . Stats . Builtin ( )
2022-02-01 18:07:23 -08:00
require . NotNil ( t , qs . Timings )
require . Greater ( t , qs . Timings . EvalTotalTime , float64 ( 0 ) )
require . NotNil ( t , qs . Samples )
require . NotNil ( t , qs . Samples . TotalQueryableSamples )
require . NotNil ( t , qs . Samples . TotalQueryableSamplesPerStep )
} ,
} ,
2022-02-10 06:17:05 -08:00
{
name : "custom handler with known value" ,
renderer : func ( ctx context . Context , s * stats . Statistics , p string ) stats . QueryStats {
if p == "known" {
return testStats { "Custom Value" }
}
return nil
} ,
param : "known" ,
expected : func ( t * testing . T , i interface { } ) {
require . IsType ( t , i , & queryData { } )
qd := i . ( * queryData )
require . NotNil ( t , qd . Stats )
j , err := json . Marshal ( qd . Stats )
require . NoError ( t , err )
require . JSONEq ( t , string ( j ) , ` { "custom":"Custom Value"} ` )
} ,
} ,
2022-02-01 18:07:23 -08:00
} {
t . Run ( tc . name , func ( t * testing . T ) {
2022-02-10 06:17:05 -08:00
before := api . statsRenderer
defer func ( ) { api . statsRenderer = before } ( )
api . statsRenderer = tc . renderer
2022-02-01 18:07:23 -08:00
for _ , method := range [ ] string { http . MethodGet , http . MethodPost } {
ctx := context . Background ( )
req , err := request ( method , tc . param )
require . NoError ( t , err )
res := api . query ( req . WithContext ( ctx ) )
assertAPIError ( t , res . err , "" )
tc . expected ( t , res . data )
res = api . queryRange ( req . WithContext ( ctx ) )
assertAPIError ( t , res . err , "" )
tc . expected ( t , res . data )
}
} )
}
}
2019-12-04 11:33:01 -08:00
func setupTestTargetRetriever ( t * testing . T ) * testTargetRetriever {
t . Helper ( )
targets := [ ] * testTargetParams {
{
Identifier : "test" ,
Labels : labels . FromMap ( map [ string ] string {
2021-08-31 08:37:32 -07:00
model . SchemeLabel : "http" ,
model . AddressLabel : "example.com:8080" ,
model . MetricsPathLabel : "/metrics" ,
model . JobLabel : "test" ,
model . ScrapeIntervalLabel : "15s" ,
model . ScrapeTimeoutLabel : "5s" ,
2019-12-04 11:33:01 -08:00
} ) ,
DiscoveredLabels : nil ,
Params : url . Values { } ,
Reports : [ ] * testReport { { scrapeStart , 70 * time . Millisecond , nil } } ,
Active : true ,
} ,
{
Identifier : "blackbox" ,
Labels : labels . FromMap ( map [ string ] string {
2021-08-31 08:37:32 -07:00
model . SchemeLabel : "http" ,
model . AddressLabel : "localhost:9115" ,
model . MetricsPathLabel : "/probe" ,
model . JobLabel : "blackbox" ,
model . ScrapeIntervalLabel : "20s" ,
model . ScrapeTimeoutLabel : "10s" ,
2019-12-04 11:33:01 -08:00
} ) ,
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 {
2021-08-31 08:37:32 -07:00
model . SchemeLabel : "http" ,
model . AddressLabel : "http://dropped.example.com:9115" ,
model . MetricsPathLabel : "/probe" ,
model . JobLabel : "blackbox" ,
model . ScrapeIntervalLabel : "30s" ,
model . ScrapeTimeoutLabel : "15s" ,
2019-12-04 11:33:01 -08:00
} ) ,
Params : url . Values { } ,
Active : false ,
} ,
}
2019-12-10 06:56:16 -08:00
return newTestTargetRetriever ( targets )
2019-12-04 11:33:01 -08:00
}
2018-06-16 10:26:37 -07:00
func setupRemote ( s storage . Storage ) * httptest . Server {
handler := http . HandlerFunc ( func ( w http . ResponseWriter , r * http . Request ) {
req , err := remote . DecodeReadRequest ( r )
if err != nil {
http . Error ( w , err . Error ( ) , http . StatusBadRequest )
return
}
resp := prompb . ReadResponse {
Results : make ( [ ] * prompb . QueryResult , len ( req . Queries ) ) ,
}
for i , query := range req . Queries {
2019-08-19 13:16:10 -07:00
matchers , err := remote . FromLabelMatchers ( query . Matchers )
2018-06-16 10:26:37 -07:00
if err != nil {
http . Error ( w , err . Error ( ) , http . StatusBadRequest )
return
}
2020-03-12 02:36:09 -07:00
var hints * storage . SelectHints
2019-08-19 13:16:10 -07:00
if query . Hints != nil {
2020-03-12 02:36:09 -07:00
hints = & storage . SelectHints {
2019-08-19 13:16:10 -07:00
Start : query . Hints . StartMs ,
End : query . Hints . EndMs ,
Step : query . Hints . StepMs ,
Func : query . Hints . Func ,
}
}
querier , err := s . Querier ( r . Context ( ) , query . StartTimestampMs , query . EndTimestampMs )
2018-06-16 10:26:37 -07:00
if err != nil {
http . Error ( w , err . Error ( ) , http . StatusInternalServerError )
return
}
defer querier . Close ( )
2020-06-09 09:57:31 -07:00
set := querier . Select ( false , hints , matchers ... )
resp . Results [ i ] , _ , err = remote . ToQueryResult ( set , 1e6 )
2018-06-16 10:26:37 -07:00
if err != nil {
http . Error ( w , err . Error ( ) , http . StatusInternalServerError )
return
}
}
if err := remote . EncodeReadResponse ( & resp , w ) ; err != nil {
http . Error ( w , err . Error ( ) , http . StatusInternalServerError )
return
}
} )
return httptest . NewServer ( handler )
}
2021-03-16 02:47:45 -07:00
func testEndpoints ( t * testing . T , api * API , tr * testTargetRetriever , es storage . ExemplarStorage , testLabelAPI bool ) {
2016-12-30 01:43:44 -08:00
start := time . Unix ( 0 , 0 )
2019-12-10 06:56:16 -08:00
type targetMetadata struct {
identifier string
metadata [ ] scrape . MetricMetadata
}
2018-06-16 10:26:37 -07:00
type test struct {
2019-12-10 06:56:16 -08:00
endpoint apiFunc
params map [ string ] string
query url . Values
response interface { }
responseLen int
errType errorType
sorter func ( interface { } )
metadata [ ] targetMetadata
2021-03-16 02:47:45 -07:00
exemplars [ ] exemplar . QueryResult
2018-06-16 10:26:37 -07:00
}
2021-10-22 01:06:44 -07:00
tests := [ ] test {
2015-06-04 09:07:57 -07:00
{
endpoint : api . query ,
query : url . Values {
"query" : [ ] string { "2" } ,
2016-12-30 01:43:44 -08:00
"time" : [ ] string { "123.4" } ,
2015-06-04 09:07:57 -07:00
} ,
response : & queryData {
2020-02-03 10:23:07 -08:00
ResultType : parser . ValueTypeScalar ,
2016-12-30 01:43:44 -08:00
Result : promql . Scalar {
V : 2 ,
T : timestamp . FromTime ( start . Add ( 123 * time . Second + 400 * time . Millisecond ) ) ,
2015-06-04 09:07:57 -07:00
} ,
} ,
} ,
{
endpoint : api . query ,
query : url . Values {
"query" : [ ] string { "0.333" } ,
"time" : [ ] string { "1970-01-01T00:02:03Z" } ,
} ,
response : & queryData {
2020-02-03 10:23:07 -08:00
ResultType : parser . ValueTypeScalar ,
2016-12-30 01:43:44 -08:00
Result : promql . Scalar {
V : 0.333 ,
T : timestamp . FromTime ( start . Add ( 123 * time . Second ) ) ,
2015-06-04 09:07:57 -07:00
} ,
} ,
} ,
{
endpoint : api . query ,
query : url . Values {
"query" : [ ] string { "0.333" } ,
"time" : [ ] string { "1970-01-01T01:02:03+01:00" } ,
} ,
response : & queryData {
2020-02-03 10:23:07 -08:00
ResultType : parser . ValueTypeScalar ,
2016-12-30 01:43:44 -08:00
Result : promql . Scalar {
V : 0.333 ,
T : timestamp . FromTime ( start . Add ( 123 * time . Second ) ) ,
2015-06-04 09:07:57 -07:00
} ,
} ,
} ,
2015-11-11 11:46:57 -08:00
{
endpoint : api . query ,
query : url . Values {
"query" : [ ] string { "0.333" } ,
} ,
response : & queryData {
2020-02-03 10:23:07 -08:00
ResultType : parser . ValueTypeScalar ,
2016-12-30 01:43:44 -08:00
Result : promql . Scalar {
V : 0.333 ,
2018-06-16 10:26:37 -07:00
T : timestamp . FromTime ( api . now ( ) ) ,
2015-11-11 11:46:57 -08:00
} ,
} ,
} ,
2015-06-09 04:44:49 -07:00
{
endpoint : api . queryRange ,
query : url . Values {
"query" : [ ] string { "time()" } ,
"start" : [ ] string { "0" } ,
"end" : [ ] string { "2" } ,
"step" : [ ] string { "1" } ,
} ,
response : & queryData {
2020-02-03 10:23:07 -08:00
ResultType : parser . ValueTypeMatrix ,
2016-12-30 01:43:44 -08:00
Result : promql . Matrix {
promql . Series {
Points : [ ] promql . Point {
{ V : 0 , T : timestamp . FromTime ( start ) } ,
{ V : 1 , T : timestamp . FromTime ( start . Add ( 1 * time . Second ) ) } ,
{ V : 2 , T : timestamp . FromTime ( start . Add ( 2 * time . Second ) ) } ,
2015-06-09 04:44:49 -07:00
} ,
2016-12-30 01:43:44 -08:00
Metric : nil ,
2015-06-09 04:44:49 -07:00
} ,
} ,
} ,
} ,
// Missing query params in range queries.
{
endpoint : api . queryRange ,
query : url . Values {
"query" : [ ] string { "time()" } ,
"end" : [ ] string { "2" } ,
"step" : [ ] string { "1" } ,
} ,
errType : errorBadData ,
} ,
{
endpoint : api . queryRange ,
query : url . Values {
"query" : [ ] string { "time()" } ,
"start" : [ ] string { "0" } ,
"step" : [ ] string { "1" } ,
} ,
errType : errorBadData ,
} ,
{
endpoint : api . queryRange ,
query : url . Values {
"query" : [ ] string { "time()" } ,
"start" : [ ] string { "0" } ,
"end" : [ ] string { "2" } ,
} ,
errType : errorBadData ,
} ,
// Bad query expression.
{
endpoint : api . query ,
query : url . Values {
"query" : [ ] string { "invalid][query" } ,
"time" : [ ] string { "1970-01-01T01:02:03+01:00" } ,
} ,
errType : errorBadData ,
} ,
{
endpoint : api . queryRange ,
query : url . Values {
"query" : [ ] string { "invalid][query" } ,
"start" : [ ] string { "0" } ,
"end" : [ ] string { "100" } ,
"step" : [ ] string { "1" } ,
} ,
errType : errorBadData ,
} ,
2017-03-16 07:16:20 -07:00
// Invalid step.
2016-08-16 06:10:02 -07:00
{
endpoint : api . queryRange ,
query : url . Values {
"query" : [ ] string { "time()" } ,
"start" : [ ] string { "1" } ,
"end" : [ ] string { "2" } ,
"step" : [ ] string { "0" } ,
} ,
errType : errorBadData ,
} ,
2017-03-16 07:16:20 -07:00
// Start after end.
2016-11-01 06:25:34 -07:00
{
endpoint : api . queryRange ,
query : url . Values {
"query" : [ ] string { "time()" } ,
"start" : [ ] string { "2" } ,
"end" : [ ] string { "1" } ,
"step" : [ ] string { "1" } ,
} ,
errType : errorBadData ,
} ,
2017-03-16 07:16:20 -07:00
// Start overflows int64 internally.
{
endpoint : api . queryRange ,
query : url . Values {
"query" : [ ] string { "time()" } ,
"start" : [ ] string { "148966367200.372" } ,
"end" : [ ] string { "1489667272.372" } ,
"step" : [ ] string { "1" } ,
} ,
errType : errorBadData ,
} ,
2015-06-09 07:09:31 -07:00
{
endpoint : api . series ,
query : url . Values {
"match[]" : [ ] string { ` test_metric2 ` } ,
} ,
2016-12-30 01:43:44 -08:00
response : [ ] labels . Labels {
labels . FromStrings ( "__name__" , "test_metric2" , "foo" , "boo" ) ,
2015-06-09 07:09:31 -07:00
} ,
} ,
2020-12-15 09:24:57 -08:00
{
endpoint : api . series ,
query : url . Values {
"match[]" : [ ] string { ` { foo=""} ` } ,
} ,
errType : errorBadData ,
} ,
2015-06-09 07:09:31 -07:00
{
endpoint : api . series ,
query : url . Values {
2015-11-05 02:23:43 -08:00
"match[]" : [ ] string { ` test_metric1 { foo=~".+o"} ` } ,
2015-06-09 07:09:31 -07:00
} ,
2016-12-30 01:43:44 -08:00
response : [ ] labels . Labels {
labels . FromStrings ( "__name__" , "test_metric1" , "foo" , "boo" ) ,
2015-06-09 07:09:31 -07:00
} ,
} ,
{
endpoint : api . series ,
query : url . Values {
2016-12-30 01:43:44 -08:00
"match[]" : [ ] string { ` test_metric1 { foo=~".+o$"} ` , ` test_metric1 { foo=~".+o"} ` } ,
2015-06-09 07:09:31 -07:00
} ,
2016-12-30 01:43:44 -08:00
response : [ ] labels . Labels {
labels . FromStrings ( "__name__" , "test_metric1" , "foo" , "boo" ) ,
2015-06-09 07:09:31 -07:00
} ,
} ,
2020-08-28 16:21:39 -07:00
// Try to overlap the selected series set as much as possible to test the result de-duplication works well.
{
endpoint : api . series ,
query : url . Values {
"match[]" : [ ] string { ` test_metric4 { foo=~".+o$"} ` , ` test_metric4 { dup=~"^1"} ` } ,
} ,
response : [ ] labels . Labels {
labels . FromStrings ( "__name__" , "test_metric4" , "dup" , "1" , "foo" , "bar" ) ,
labels . FromStrings ( "__name__" , "test_metric4" , "dup" , "1" , "foo" , "boo" ) ,
labels . FromStrings ( "__name__" , "test_metric4" , "foo" , "boo" ) ,
} ,
} ,
2015-06-09 07:09:31 -07:00
{
endpoint : api . series ,
query : url . Values {
2015-11-05 02:23:43 -08:00
"match[]" : [ ] string { ` test_metric1 { foo=~".+o"} ` , ` none ` } ,
2015-06-09 07:09:31 -07:00
} ,
2016-12-30 01:43:44 -08:00
response : [ ] labels . Labels {
labels . FromStrings ( "__name__" , "test_metric1" , "foo" , "boo" ) ,
2015-06-09 07:09:31 -07:00
} ,
} ,
2016-05-11 14:59:52 -07:00
// Start and end before series starts.
{
endpoint : api . series ,
query : url . Values {
"match[]" : [ ] string { ` test_metric2 ` } ,
"start" : [ ] string { "-2" } ,
"end" : [ ] string { "-1" } ,
} ,
2016-12-30 01:43:44 -08:00
response : [ ] labels . Labels { } ,
2016-05-11 14:59:52 -07:00
} ,
// Start and end after series ends.
{
endpoint : api . series ,
query : url . Values {
"match[]" : [ ] string { ` test_metric2 ` } ,
"start" : [ ] string { "100000" } ,
"end" : [ ] string { "100001" } ,
} ,
2016-12-30 01:43:44 -08:00
response : [ ] labels . Labels { } ,
2016-05-11 14:59:52 -07:00
} ,
// Start before series starts, end after series ends.
{
endpoint : api . series ,
query : url . Values {
"match[]" : [ ] string { ` test_metric2 ` } ,
"start" : [ ] string { "-1" } ,
"end" : [ ] string { "100000" } ,
} ,
2016-12-30 01:43:44 -08:00
response : [ ] labels . Labels {
labels . FromStrings ( "__name__" , "test_metric2" , "foo" , "boo" ) ,
2016-05-11 14:59:52 -07:00
} ,
} ,
// Start and end within series.
{
endpoint : api . series ,
query : url . Values {
"match[]" : [ ] string { ` test_metric2 ` } ,
"start" : [ ] string { "1" } ,
"end" : [ ] string { "100" } ,
} ,
2016-12-30 01:43:44 -08:00
response : [ ] labels . Labels {
labels . FromStrings ( "__name__" , "test_metric2" , "foo" , "boo" ) ,
2016-05-11 14:59:52 -07:00
} ,
} ,
// Start within series, end after.
{
endpoint : api . series ,
query : url . Values {
"match[]" : [ ] string { ` test_metric2 ` } ,
"start" : [ ] string { "1" } ,
"end" : [ ] string { "100000" } ,
} ,
2016-12-30 01:43:44 -08:00
response : [ ] labels . Labels {
labels . FromStrings ( "__name__" , "test_metric2" , "foo" , "boo" ) ,
2016-05-11 14:59:52 -07:00
} ,
} ,
// Start before series, end within series.
{
endpoint : api . series ,
query : url . Values {
"match[]" : [ ] string { ` test_metric2 ` } ,
"start" : [ ] string { "-1" } ,
"end" : [ ] string { "1" } ,
} ,
2016-12-30 01:43:44 -08:00
response : [ ] labels . Labels {
labels . FromStrings ( "__name__" , "test_metric2" , "foo" , "boo" ) ,
2016-05-11 14:59:52 -07:00
} ,
} ,
2015-06-09 07:09:31 -07:00
// Missing match[] query params in series requests.
{
endpoint : api . series ,
errType : errorBadData ,
} ,
{
endpoint : api . dropSeries ,
2017-07-06 05:38:40 -07:00
errType : errorInternal ,
2015-06-09 07:09:31 -07:00
} ,
2017-05-11 08:09:24 -07:00
{
2016-12-02 04:31:43 -08:00
endpoint : api . targets ,
2017-01-13 08:15:04 -08:00
response : & TargetDiscovery {
2018-10-25 01:19:20 -07:00
ActiveTargets : [ ] * Target {
{
DiscoveredLabels : map [ string ] string { } ,
Labels : map [ string ] string {
"job" : "blackbox" ,
2018-09-26 02:20:56 -07:00
} ,
2019-11-11 13:42:24 -08:00
ScrapePool : "blackbox" ,
ScrapeURL : "http://localhost:9115/probe?target=example.com" ,
2020-02-17 09:19:15 -08:00
GlobalURL : "http://localhost:9115/probe?target=example.com" ,
2019-11-11 13:42:24 -08:00
Health : "down" ,
2020-02-17 09:19:15 -08:00
LastError : "failed: missing port in address" ,
2019-11-11 13:42:24 -08:00
LastScrape : scrapeStart ,
LastScrapeDuration : 0.1 ,
2021-08-31 08:37:32 -07:00
ScrapeInterval : "20s" ,
ScrapeTimeout : "10s" ,
2018-10-25 01:19:20 -07:00
} ,
{
DiscoveredLabels : map [ string ] string { } ,
Labels : map [ string ] string {
"job" : "test" ,
} ,
2019-11-11 13:42:24 -08:00
ScrapePool : "test" ,
ScrapeURL : "http://example.com:8080/metrics" ,
2020-02-17 09:19:15 -08:00
GlobalURL : "http://example.com:8080/metrics" ,
2019-11-11 13:42:24 -08:00
Health : "up" ,
LastError : "" ,
LastScrape : scrapeStart ,
LastScrapeDuration : 0.07 ,
2021-08-31 08:37:32 -07:00
ScrapeInterval : "15s" ,
ScrapeTimeout : "5s" ,
2019-11-11 13:42:24 -08:00
} ,
} ,
DroppedTargets : [ ] * DroppedTarget {
{
DiscoveredLabels : map [ string ] string {
2021-08-31 08:37:32 -07:00
"__address__" : "http://dropped.example.com:9115" ,
"__metrics_path__" : "/probe" ,
"__scheme__" : "http" ,
"job" : "blackbox" ,
"__scrape_interval__" : "30s" ,
"__scrape_timeout__" : "15s" ,
2019-11-11 13:42:24 -08:00
} ,
2017-01-13 08:15:04 -08:00
} ,
2016-12-02 04:31:43 -08:00
} ,
2019-11-11 13:42:24 -08:00
} ,
} ,
{
endpoint : api . targets ,
query : url . Values {
"state" : [ ] string { "any" } ,
} ,
response : & TargetDiscovery {
ActiveTargets : [ ] * Target {
{
DiscoveredLabels : map [ string ] string { } ,
Labels : map [ string ] string {
"job" : "blackbox" ,
} ,
ScrapePool : "blackbox" ,
ScrapeURL : "http://localhost:9115/probe?target=example.com" ,
2020-02-17 09:19:15 -08:00
GlobalURL : "http://localhost:9115/probe?target=example.com" ,
2019-11-11 13:42:24 -08:00
Health : "down" ,
2020-02-17 09:19:15 -08:00
LastError : "failed: missing port in address" ,
2019-11-11 13:42:24 -08:00
LastScrape : scrapeStart ,
LastScrapeDuration : 0.1 ,
2021-08-31 08:37:32 -07:00
ScrapeInterval : "20s" ,
ScrapeTimeout : "10s" ,
2019-11-11 13:42:24 -08:00
} ,
{
DiscoveredLabels : map [ string ] string { } ,
Labels : map [ string ] string {
"job" : "test" ,
} ,
ScrapePool : "test" ,
ScrapeURL : "http://example.com:8080/metrics" ,
2020-02-17 09:19:15 -08:00
GlobalURL : "http://example.com:8080/metrics" ,
2019-11-11 13:42:24 -08:00
Health : "up" ,
LastError : "" ,
LastScrape : scrapeStart ,
LastScrapeDuration : 0.07 ,
2021-08-31 08:37:32 -07:00
ScrapeInterval : "15s" ,
ScrapeTimeout : "5s" ,
2019-11-11 13:42:24 -08:00
} ,
} ,
DroppedTargets : [ ] * DroppedTarget {
{
DiscoveredLabels : map [ string ] string {
2021-08-31 08:37:32 -07:00
"__address__" : "http://dropped.example.com:9115" ,
"__metrics_path__" : "/probe" ,
"__scheme__" : "http" ,
"job" : "blackbox" ,
"__scrape_interval__" : "30s" ,
"__scrape_timeout__" : "15s" ,
2019-11-11 13:42:24 -08:00
} ,
} ,
} ,
} ,
} ,
{
endpoint : api . targets ,
query : url . Values {
"state" : [ ] string { "active" } ,
} ,
response : & TargetDiscovery {
ActiveTargets : [ ] * Target {
{
DiscoveredLabels : map [ string ] string { } ,
Labels : map [ string ] string {
"job" : "blackbox" ,
} ,
ScrapePool : "blackbox" ,
ScrapeURL : "http://localhost:9115/probe?target=example.com" ,
2020-02-17 09:19:15 -08:00
GlobalURL : "http://localhost:9115/probe?target=example.com" ,
2019-11-11 13:42:24 -08:00
Health : "down" ,
2020-02-17 09:19:15 -08:00
LastError : "failed: missing port in address" ,
2019-11-11 13:42:24 -08:00
LastScrape : scrapeStart ,
LastScrapeDuration : 0.1 ,
2021-08-31 08:37:32 -07:00
ScrapeInterval : "20s" ,
ScrapeTimeout : "10s" ,
2019-11-11 13:42:24 -08:00
} ,
{
DiscoveredLabels : map [ string ] string { } ,
Labels : map [ string ] string {
"job" : "test" ,
} ,
ScrapePool : "test" ,
ScrapeURL : "http://example.com:8080/metrics" ,
2020-02-17 09:19:15 -08:00
GlobalURL : "http://example.com:8080/metrics" ,
2019-11-11 13:42:24 -08:00
Health : "up" ,
LastError : "" ,
LastScrape : scrapeStart ,
LastScrapeDuration : 0.07 ,
2021-08-31 08:37:32 -07:00
ScrapeInterval : "15s" ,
ScrapeTimeout : "5s" ,
2019-11-11 13:42:24 -08:00
} ,
} ,
DroppedTargets : [ ] * DroppedTarget { } ,
} ,
} ,
{
endpoint : api . targets ,
query : url . Values {
"state" : [ ] string { "Dropped" } ,
} ,
response : & TargetDiscovery {
ActiveTargets : [ ] * Target { } ,
2018-10-25 01:19:20 -07:00
DroppedTargets : [ ] * DroppedTarget {
{
DiscoveredLabels : map [ string ] string {
2021-08-31 08:37:32 -07:00
"__address__" : "http://dropped.example.com:9115" ,
"__metrics_path__" : "/probe" ,
"__scheme__" : "http" ,
"job" : "blackbox" ,
"__scrape_interval__" : "30s" ,
"__scrape_timeout__" : "15s" ,
2018-02-21 09:26:18 -08:00
} ,
} ,
} ,
2016-12-02 04:31:43 -08:00
} ,
2017-05-11 08:09:24 -07:00
} ,
2019-12-04 11:33:01 -08:00
// With a matching metric.
{
endpoint : api . targetMetadata ,
query : url . Values {
"metric" : [ ] string { "go_threads" } ,
} ,
2019-12-10 06:56:16 -08:00
metadata : [ ] targetMetadata {
{
identifier : "test" ,
metadata : [ ] scrape . MetricMetadata {
{
Metric : "go_threads" ,
Type : textparse . MetricTypeGauge ,
Help : "Number of OS threads created." ,
Unit : "" ,
} ,
} ,
} ,
} ,
2019-12-04 11:33:01 -08:00
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\"}" } ,
} ,
2019-12-10 06:56:16 -08:00
metadata : [ ] targetMetadata {
{
identifier : "blackbox" ,
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 : "" ,
} ,
} ,
} ,
} ,
2019-12-04 11:33:01 -08:00
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 ,
2019-12-10 06:56:16 -08:00
metadata : [ ] targetMetadata {
{
identifier : "test" ,
metadata : [ ] scrape . MetricMetadata {
{
Metric : "go_threads" ,
Type : textparse . MetricTypeGauge ,
Help : "Number of OS threads created." ,
Unit : "" ,
} ,
} ,
} ,
{
identifier : "blackbox" ,
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 : "" ,
} ,
} ,
} ,
} ,
2019-12-04 11:33:01 -08:00
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 : "" ,
} ,
} ,
2019-12-09 13:36:38 -08:00
sorter : func ( m interface { } ) {
sort . Slice ( m . ( [ ] metricMetadata ) , func ( i , j int ) bool {
s := m . ( [ ] metricMetadata )
return s [ i ] . Metric < s [ j ] . Metric
} )
} ,
2019-12-04 11:33:01 -08:00
} ,
// Without a matching metric.
{
endpoint : api . targetMetadata ,
query : url . Values {
"match_target" : [ ] string { "{job=\"non-existentblackbox\"}" } ,
} ,
2019-12-10 06:56:16 -08:00
response : [ ] metricMetadata { } ,
2019-12-04 11:33:01 -08:00
} ,
2015-06-09 07:09:31 -07:00
{
2017-01-13 01:20:11 -08:00
endpoint : api . alertmanagers ,
response : & AlertmanagerDiscovery {
ActiveAlertmanagers : [ ] * AlertmanagerTarget {
2017-04-05 06:24:22 -07:00
{
2017-01-13 01:20:11 -08:00
URL : "http://alertmanager.example.com:8080/api/v1/alerts" ,
} ,
} ,
2018-02-21 01:00:07 -08:00
DroppedAlertmanagers : [ ] * AlertmanagerTarget {
{
URL : "http://dropped.alertmanager.example.com:8080/api/v1/alerts" ,
} ,
} ,
2017-01-13 01:20:11 -08:00
} ,
2015-06-09 07:09:31 -07:00
} ,
2019-12-10 06:56:16 -08:00
// With metadata available.
{
endpoint : api . metricMetadata ,
metadata : [ ] targetMetadata {
{
identifier : "test" ,
metadata : [ ] scrape . MetricMetadata {
{
Metric : "prometheus_engine_query_duration_seconds" ,
Type : textparse . MetricTypeSummary ,
Help : "Query timings" ,
Unit : "" ,
} ,
{
Metric : "go_info" ,
Type : textparse . MetricTypeGauge ,
Help : "Information about the Go environment." ,
Unit : "" ,
} ,
} ,
} ,
} ,
response : map [ string ] [ ] metadata {
"prometheus_engine_query_duration_seconds" : { { textparse . MetricTypeSummary , "Query timings" , "" } } ,
"go_info" : { { textparse . MetricTypeGauge , "Information about the Go environment." , "" } } ,
} ,
} ,
// With duplicate metadata for a metric that comes from different targets.
{
endpoint : api . metricMetadata ,
metadata : [ ] targetMetadata {
{
identifier : "test" ,
metadata : [ ] scrape . MetricMetadata {
{
Metric : "go_threads" ,
Type : textparse . MetricTypeGauge ,
Help : "Number of OS threads created" ,
Unit : "" ,
} ,
} ,
} ,
{
identifier : "blackbox" ,
metadata : [ ] scrape . MetricMetadata {
{
Metric : "go_threads" ,
Type : textparse . MetricTypeGauge ,
Help : "Number of OS threads created" ,
Unit : "" ,
} ,
} ,
} ,
} ,
response : map [ string ] [ ] metadata {
"go_threads" : { { textparse . MetricTypeGauge , "Number of OS threads created" , "" } } ,
} ,
} ,
// With non-duplicate metadata for the same metric from different targets.
{
endpoint : api . metricMetadata ,
metadata : [ ] targetMetadata {
{
identifier : "test" ,
metadata : [ ] scrape . MetricMetadata {
{
Metric : "go_threads" ,
Type : textparse . MetricTypeGauge ,
Help : "Number of OS threads created" ,
Unit : "" ,
} ,
} ,
} ,
{
identifier : "blackbox" ,
metadata : [ ] scrape . MetricMetadata {
{
Metric : "go_threads" ,
Type : textparse . MetricTypeGauge ,
Help : "Number of OS threads that were created." ,
Unit : "" ,
} ,
} ,
} ,
} ,
response : map [ string ] [ ] metadata {
2020-06-22 07:29:35 -07:00
"go_threads" : {
2019-12-10 06:56:16 -08:00
{ textparse . MetricTypeGauge , "Number of OS threads created" , "" } ,
{ textparse . MetricTypeGauge , "Number of OS threads that were created." , "" } ,
} ,
} ,
sorter : func ( m interface { } ) {
v := m . ( map [ string ] [ ] metadata ) [ "go_threads" ]
sort . Slice ( v , func ( i , j int ) bool {
return v [ i ] . Help < v [ j ] . Help
} )
} ,
} ,
2019-12-10 07:15:13 -08:00
// With a limit for the number of metrics returned.
2019-12-10 06:56:16 -08:00
{
endpoint : api . metricMetadata ,
query : url . Values {
"limit" : [ ] string { "2" } ,
} ,
metadata : [ ] targetMetadata {
{
identifier : "test" ,
metadata : [ ] scrape . MetricMetadata {
{
Metric : "go_threads" ,
Type : textparse . MetricTypeGauge ,
Help : "Number of OS threads created" ,
Unit : "" ,
} ,
{
Metric : "prometheus_engine_query_duration_seconds" ,
Type : textparse . MetricTypeSummary ,
Help : "Query Timmings." ,
Unit : "" ,
} ,
} ,
} ,
{
identifier : "blackbox" ,
metadata : [ ] scrape . MetricMetadata {
{
Metric : "go_gc_duration_seconds" ,
Type : textparse . MetricTypeSummary ,
Help : "A summary of the GC invocation durations." ,
Unit : "" ,
} ,
} ,
} ,
} ,
responseLen : 2 ,
} ,
2019-12-10 07:22:10 -08:00
// When requesting a specific metric that is present.
{
endpoint : api . metricMetadata ,
query : url . Values { "metric" : [ ] string { "go_threads" } } ,
metadata : [ ] targetMetadata {
{
identifier : "test" ,
metadata : [ ] scrape . MetricMetadata {
{
Metric : "go_threads" ,
Type : textparse . MetricTypeGauge ,
Help : "Number of OS threads created" ,
Unit : "" ,
} ,
} ,
} ,
{
identifier : "blackbox" ,
metadata : [ ] scrape . MetricMetadata {
{
Metric : "go_gc_duration_seconds" ,
Type : textparse . MetricTypeSummary ,
Help : "A summary of the GC invocation durations." ,
Unit : "" ,
} ,
{
Metric : "go_threads" ,
Type : textparse . MetricTypeGauge ,
Help : "Number of OS threads that were created." ,
Unit : "" ,
} ,
} ,
} ,
} ,
response : map [ string ] [ ] metadata {
2020-06-22 07:29:35 -07:00
"go_threads" : {
2019-12-10 07:22:10 -08:00
{ textparse . MetricTypeGauge , "Number of OS threads created" , "" } ,
{ textparse . MetricTypeGauge , "Number of OS threads that were created." , "" } ,
} ,
} ,
sorter : func ( m interface { } ) {
v := m . ( map [ string ] [ ] metadata ) [ "go_threads" ]
sort . Slice ( v , func ( i , j int ) bool {
return v [ i ] . Help < v [ j ] . Help
} )
} ,
} ,
// With a specific metric that is not present.
{
endpoint : api . metricMetadata ,
query : url . Values { "metric" : [ ] string { "go_gc_duration_seconds" } } ,
metadata : [ ] targetMetadata {
{
identifier : "test" ,
metadata : [ ] scrape . MetricMetadata {
{
Metric : "go_threads" ,
Type : textparse . MetricTypeGauge ,
Help : "Number of OS threads created" ,
Unit : "" ,
} ,
} ,
} ,
} ,
response : map [ string ] [ ] metadata { } ,
} ,
2019-12-10 07:15:13 -08:00
// With no available metadata.
2019-12-10 06:56:16 -08:00
{
endpoint : api . metricMetadata ,
response : map [ string ] [ ] metadata { } ,
} ,
2017-05-11 08:09:24 -07:00
{
endpoint : api . serveConfig ,
response : & prometheusConfig {
YAML : samplePrometheusCfg . String ( ) ,
} ,
} ,
api: Added v1/status/flags endpoint. (#3864)
Endpoint URL: /api/v1/status/flags
Example Output:
```json
{
"status": "success",
"data": {
"alertmanager.notification-queue-capacity": "10000",
"alertmanager.timeout": "10s",
"completion-bash": "false",
"completion-script-bash": "false",
"completion-script-zsh": "false",
"config.file": "my_cool_prometheus.yaml",
"help": "false",
"help-long": "false",
"help-man": "false",
"log.level": "info",
"query.lookback-delta": "5m",
"query.max-concurrency": "20",
"query.timeout": "2m",
"storage.tsdb.max-block-duration": "36h",
"storage.tsdb.min-block-duration": "2h",
"storage.tsdb.no-lockfile": "false",
"storage.tsdb.path": "data/",
"storage.tsdb.retention": "15d",
"version": "false",
"web.console.libraries": "console_libraries",
"web.console.templates": "consoles",
"web.enable-admin-api": "false",
"web.enable-lifecycle": "false",
"web.external-url": "",
"web.listen-address": "0.0.0.0:9090",
"web.max-connections": "512",
"web.read-timeout": "5m",
"web.route-prefix": "/",
"web.user-assets": ""
}
}
```
Signed-off-by: Bartek Plotka <bwplotka@gmail.com>
2018-02-21 00:49:02 -08:00
{
endpoint : api . serveFlags ,
response : sampleFlagMap ,
} ,
2018-03-25 09:50:34 -07:00
{
endpoint : api . alerts ,
response : & AlertDiscovery {
2018-06-27 00:15:17 -07:00
Alerts : [ ] * Alert { } ,
2018-03-25 09:50:34 -07:00
} ,
} ,
{
endpoint : api . rules ,
2018-06-27 00:15:17 -07:00
response : & RuleDiscovery {
RuleGroups : [ ] * RuleGroup {
2018-03-25 09:50:34 -07:00
{
2018-06-27 00:15:17 -07:00
Name : "grp" ,
File : "/path/to/file" ,
Interval : 1 ,
2022-01-11 19:44:22 -08:00
Limit : 0 ,
2021-11-21 09:00:27 -08:00
Rules : [ ] Rule {
AlertingRule {
2019-12-09 14:42:59 -08:00
State : "inactive" ,
2018-06-27 00:15:17 -07:00
Name : "test_metric3" ,
Query : "absent(test_metric3) != 1" ,
Duration : 1 ,
Labels : labels . Labels { } ,
Annotations : labels . Labels { } ,
Alerts : [ ] * Alert { } ,
2018-08-23 06:00:10 -07:00
Health : "unknown" ,
2018-06-27 00:15:17 -07:00
Type : "alerting" ,
} ,
2021-11-21 09:00:27 -08:00
AlertingRule {
2019-12-09 14:42:59 -08:00
State : "inactive" ,
2018-06-27 00:15:17 -07:00
Name : "test_metric4" ,
Query : "up == 1" ,
Duration : 1 ,
Labels : labels . Labels { } ,
Annotations : labels . Labels { } ,
Alerts : [ ] * Alert { } ,
2018-08-23 06:00:10 -07:00
Health : "unknown" ,
2018-06-27 00:15:17 -07:00
Type : "alerting" ,
2018-03-25 09:50:34 -07:00
} ,
2021-11-21 09:00:27 -08:00
RecordingRule {
2018-06-27 00:15:17 -07:00
Name : "recording-rule-1" ,
Query : "vector(1)" ,
Labels : labels . Labels { } ,
2018-08-23 06:00:10 -07:00
Health : "unknown" ,
2018-06-27 00:15:17 -07:00
Type : "recording" ,
2018-03-25 09:50:34 -07:00
} ,
} ,
} ,
} ,
} ,
} ,
2019-12-09 14:42:59 -08:00
{
endpoint : api . rules ,
query : url . Values {
"type" : [ ] string { "alert" } ,
} ,
response : & RuleDiscovery {
RuleGroups : [ ] * RuleGroup {
{
Name : "grp" ,
File : "/path/to/file" ,
Interval : 1 ,
2022-01-11 19:44:22 -08:00
Limit : 0 ,
2021-11-21 09:00:27 -08:00
Rules : [ ] Rule {
AlertingRule {
2019-12-09 14:42:59 -08:00
State : "inactive" ,
Name : "test_metric3" ,
Query : "absent(test_metric3) != 1" ,
Duration : 1 ,
Labels : labels . Labels { } ,
Annotations : labels . Labels { } ,
Alerts : [ ] * Alert { } ,
Health : "unknown" ,
Type : "alerting" ,
} ,
2021-11-21 09:00:27 -08:00
AlertingRule {
2019-12-09 14:42:59 -08:00
State : "inactive" ,
Name : "test_metric4" ,
Query : "up == 1" ,
Duration : 1 ,
Labels : labels . Labels { } ,
Annotations : labels . Labels { } ,
Alerts : [ ] * Alert { } ,
Health : "unknown" ,
Type : "alerting" ,
} ,
} ,
} ,
} ,
} ,
} ,
{
endpoint : api . rules ,
query : url . Values {
"type" : [ ] string { "record" } ,
} ,
response : & RuleDiscovery {
RuleGroups : [ ] * RuleGroup {
{
Name : "grp" ,
File : "/path/to/file" ,
Interval : 1 ,
2022-01-11 19:44:22 -08:00
Limit : 0 ,
2021-11-21 09:00:27 -08:00
Rules : [ ] Rule {
RecordingRule {
2019-12-09 14:42:59 -08:00
Name : "recording-rule-1" ,
Query : "vector(1)" ,
Labels : labels . Labels { } ,
Health : "unknown" ,
Type : "recording" ,
} ,
} ,
} ,
} ,
} ,
} ,
2021-03-16 02:47:45 -07:00
{
endpoint : api . queryExemplars ,
query : url . Values {
"query" : [ ] string { ` test_metric3 { foo="boo"} - test_metric4 { foo="bar"} ` } ,
"start" : [ ] string { "0" } ,
"end" : [ ] string { "4" } ,
} ,
// Note extra integer length of timestamps for exemplars because of millisecond preservation
// of timestamps within Prometheus (see timestamp package).
response : [ ] exemplar . QueryResult {
{
SeriesLabels : labels . FromStrings ( "__name__" , "test_metric3" , "foo" , "boo" , "dup" , "1" ) ,
Exemplars : [ ] exemplar . Exemplar {
{
Labels : labels . FromStrings ( "id" , "abc" ) ,
Value : 10 ,
Ts : timestamp . FromTime ( start . Add ( 2 * time . Second ) ) ,
} ,
} ,
} ,
{
SeriesLabels : labels . FromStrings ( "__name__" , "test_metric4" , "foo" , "bar" , "dup" , "1" ) ,
Exemplars : [ ] exemplar . Exemplar {
{
Labels : labels . FromStrings ( "id" , "lul" ) ,
Value : 10 ,
Ts : timestamp . FromTime ( start . Add ( 4 * time . Second ) ) ,
} ,
} ,
} ,
} ,
} ,
{
endpoint : api . queryExemplars ,
query : url . Values {
"query" : [ ] string { ` { foo="boo"} ` } ,
"start" : [ ] string { "4" } ,
"end" : [ ] string { "4.1" } ,
} ,
response : [ ] exemplar . QueryResult {
{
SeriesLabels : labels . FromStrings ( "__name__" , "test_metric3" , "foo" , "boo" , "dup" , "1" ) ,
Exemplars : [ ] exemplar . Exemplar {
{
Labels : labels . FromStrings ( "id" , "abc2" ) ,
Value : 10 ,
Ts : 4053 ,
} ,
} ,
} ,
} ,
} ,
{
endpoint : api . queryExemplars ,
query : url . Values {
"query" : [ ] string { ` { foo="boo"} ` } ,
} ,
response : [ ] exemplar . QueryResult {
{
SeriesLabels : labels . FromStrings ( "__name__" , "test_metric3" , "foo" , "boo" , "dup" , "1" ) ,
Exemplars : [ ] exemplar . Exemplar {
{
Labels : labels . FromStrings ( "id" , "abc" ) ,
Value : 10 ,
Ts : 2000 ,
} ,
{
Labels : labels . FromStrings ( "id" , "abc2" ) ,
Value : 10 ,
Ts : 4053 ,
} ,
} ,
} ,
} ,
} ,
{
endpoint : api . queryExemplars ,
query : url . Values {
"query" : [ ] string { ` { __name__="test_metric5"} ` } ,
} ,
response : [ ] exemplar . QueryResult { } ,
} ,
2015-06-04 09:07:57 -07:00
}
2018-06-16 10:26:37 -07:00
if testLabelAPI {
tests = append ( tests , [ ] test {
{
endpoint : api . labelValues ,
params : map [ string ] string {
"name" : "__name__" ,
} ,
response : [ ] string {
"test_metric1" ,
"test_metric2" ,
2020-08-28 16:21:39 -07:00
"test_metric3" ,
"test_metric4" ,
2018-06-16 10:26:37 -07:00
} ,
} ,
{
endpoint : api . labelValues ,
params : map [ string ] string {
"name" : "foo" ,
} ,
response : [ ] string {
"bar" ,
"boo" ,
} ,
} ,
// Bad name parameter.
{
endpoint : api . labelValues ,
params : map [ string ] string {
"name" : "not!!!allowed" ,
} ,
errType : errorBadData ,
} ,
2020-05-30 05:50:09 -07:00
// Start and end before LabelValues starts.
{
endpoint : api . labelValues ,
params : map [ string ] string {
"name" : "foo" ,
} ,
query : url . Values {
"start" : [ ] string { "-2" } ,
"end" : [ ] string { "-1" } ,
} ,
response : [ ] string { } ,
} ,
// Start and end within LabelValues.
{
endpoint : api . labelValues ,
params : map [ string ] string {
"name" : "foo" ,
} ,
query : url . Values {
"start" : [ ] string { "1" } ,
"end" : [ ] string { "100" } ,
} ,
response : [ ] string {
"bar" ,
"boo" ,
} ,
} ,
// Start before LabelValues, end within LabelValues.
{
endpoint : api . labelValues ,
params : map [ string ] string {
"name" : "foo" ,
} ,
query : url . Values {
"start" : [ ] string { "-1" } ,
"end" : [ ] string { "3" } ,
} ,
response : [ ] string {
"bar" ,
"boo" ,
} ,
} ,
// Start before LabelValues starts, end after LabelValues ends.
{
endpoint : api . labelValues ,
params : map [ string ] string {
"name" : "foo" ,
} ,
query : url . Values {
"start" : [ ] string { "1969-12-31T00:00:00Z" } ,
"end" : [ ] string { "1970-02-01T00:02:03Z" } ,
} ,
response : [ ] string {
"bar" ,
"boo" ,
} ,
} ,
// Start with bad data, end within LabelValues.
{
endpoint : api . labelValues ,
params : map [ string ] string {
"name" : "foo" ,
} ,
query : url . Values {
"start" : [ ] string { "boop" } ,
"end" : [ ] string { "1" } ,
} ,
errType : errorBadData ,
} ,
// Start within LabelValues, end after.
{
endpoint : api . labelValues ,
params : map [ string ] string {
"name" : "foo" ,
} ,
query : url . Values {
"start" : [ ] string { "1" } ,
"end" : [ ] string { "100000000" } ,
} ,
response : [ ] string {
"bar" ,
"boo" ,
} ,
} ,
// Start and end after LabelValues ends.
{
endpoint : api . labelValues ,
params : map [ string ] string {
"name" : "foo" ,
} ,
query : url . Values {
"start" : [ ] string { "148966367200.372" } ,
"end" : [ ] string { "148966367200.972" } ,
} ,
response : [ ] string { } ,
} ,
// Only provide Start within LabelValues, don't provide an end time.
{
endpoint : api . labelValues ,
params : map [ string ] string {
"name" : "foo" ,
} ,
query : url . Values {
"start" : [ ] string { "2" } ,
} ,
response : [ ] string {
"bar" ,
"boo" ,
} ,
} ,
// Only provide end within LabelValues, don't provide a start time.
{
endpoint : api . labelValues ,
params : map [ string ] string {
"name" : "foo" ,
} ,
query : url . Values {
"end" : [ ] string { "100" } ,
} ,
response : [ ] string {
"bar" ,
"boo" ,
} ,
} ,
2020-12-22 03:02:19 -08:00
// Label values with bad matchers.
{
endpoint : api . labelValues ,
params : map [ string ] string {
"name" : "foo" ,
} ,
query : url . Values {
"match[]" : [ ] string { ` { foo="" ` , ` test_metric2 ` } ,
} ,
errType : errorBadData ,
} ,
// Label values with empty matchers.
{
endpoint : api . labelValues ,
params : map [ string ] string {
"name" : "foo" ,
} ,
query : url . Values {
"match[]" : [ ] string { ` { foo=""} ` } ,
} ,
errType : errorBadData ,
} ,
// Label values with matcher.
{
endpoint : api . labelValues ,
params : map [ string ] string {
"name" : "foo" ,
} ,
query : url . Values {
"match[]" : [ ] string { ` test_metric2 ` } ,
} ,
response : [ ] string {
"boo" ,
} ,
} ,
// Label values with matcher.
{
endpoint : api . labelValues ,
params : map [ string ] string {
"name" : "foo" ,
} ,
query : url . Values {
"match[]" : [ ] string { ` test_metric1 ` } ,
} ,
response : [ ] string {
"bar" ,
"boo" ,
} ,
} ,
// Label values with matcher using label filter.
{
endpoint : api . labelValues ,
params : map [ string ] string {
"name" : "foo" ,
} ,
query : url . Values {
"match[]" : [ ] string { ` test_metric1 { foo="bar"} ` } ,
} ,
response : [ ] string {
"bar" ,
} ,
} ,
// Label values with matcher and time range.
{
endpoint : api . labelValues ,
params : map [ string ] string {
"name" : "foo" ,
} ,
query : url . Values {
"match[]" : [ ] string { ` test_metric1 ` } ,
"start" : [ ] string { "1" } ,
"end" : [ ] string { "100000000" } ,
} ,
response : [ ] string {
"bar" ,
"boo" ,
} ,
} ,
2021-02-09 09:38:35 -08:00
// Try to overlap the selected series set as much as possible to test that the value de-duplication works.
{
endpoint : api . labelValues ,
params : map [ string ] string {
"name" : "foo" ,
} ,
query : url . Values {
"match[]" : [ ] string { ` test_metric4 { dup=~"^1"} ` , ` test_metric4 { foo=~".+o$"} ` } ,
} ,
response : [ ] string {
"bar" ,
"boo" ,
} ,
} ,
2018-11-19 02:21:14 -08:00
// Label names.
{
endpoint : api . labelNames ,
2020-08-28 16:21:39 -07:00
response : [ ] string { "__name__" , "dup" , "foo" } ,
2018-11-19 02:21:14 -08:00
} ,
2020-05-30 05:50:09 -07:00
// Start and end before Label names starts.
{
endpoint : api . labelNames ,
query : url . Values {
"start" : [ ] string { "-2" } ,
"end" : [ ] string { "-1" } ,
} ,
response : [ ] string { } ,
} ,
// Start and end within Label names.
{
endpoint : api . labelNames ,
query : url . Values {
"start" : [ ] string { "1" } ,
"end" : [ ] string { "100" } ,
} ,
2020-08-28 16:21:39 -07:00
response : [ ] string { "__name__" , "dup" , "foo" } ,
2020-05-30 05:50:09 -07:00
} ,
// Start before Label names, end within Label names.
{
endpoint : api . labelNames ,
query : url . Values {
"start" : [ ] string { "-1" } ,
"end" : [ ] string { "10" } ,
} ,
2020-08-28 16:21:39 -07:00
response : [ ] string { "__name__" , "dup" , "foo" } ,
2020-05-30 05:50:09 -07:00
} ,
// Start before Label names starts, end after Label names ends.
{
endpoint : api . labelNames ,
query : url . Values {
"start" : [ ] string { "-1" } ,
"end" : [ ] string { "100000" } ,
} ,
2020-08-28 16:21:39 -07:00
response : [ ] string { "__name__" , "dup" , "foo" } ,
2020-05-30 05:50:09 -07:00
} ,
// Start with bad data for Label names, end within Label names.
{
endpoint : api . labelNames ,
query : url . Values {
"start" : [ ] string { "boop" } ,
"end" : [ ] string { "1" } ,
} ,
errType : errorBadData ,
} ,
// Start within Label names, end after.
{
endpoint : api . labelNames ,
query : url . Values {
"start" : [ ] string { "1" } ,
"end" : [ ] string { "1000000006" } ,
} ,
2020-08-28 16:21:39 -07:00
response : [ ] string { "__name__" , "dup" , "foo" } ,
2020-05-30 05:50:09 -07:00
} ,
// Start and end after Label names ends.
{
endpoint : api . labelNames ,
query : url . Values {
"start" : [ ] string { "148966367200.372" } ,
"end" : [ ] string { "148966367200.972" } ,
} ,
response : [ ] string { } ,
} ,
// Only provide Start within Label names, don't provide an end time.
{
endpoint : api . labelNames ,
query : url . Values {
"start" : [ ] string { "4" } ,
} ,
2020-08-28 16:21:39 -07:00
response : [ ] string { "__name__" , "dup" , "foo" } ,
2020-05-30 05:50:09 -07:00
} ,
// Only provide End within Label names, don't provide a start time.
{
endpoint : api . labelNames ,
query : url . Values {
"end" : [ ] string { "20" } ,
} ,
2020-08-28 16:21:39 -07:00
response : [ ] string { "__name__" , "dup" , "foo" } ,
2020-05-30 05:50:09 -07:00
} ,
2020-12-22 03:02:19 -08:00
// Label names with bad matchers.
{
endpoint : api . labelNames ,
query : url . Values {
"match[]" : [ ] string { ` { foo="" ` , ` test_metric2 ` } ,
} ,
errType : errorBadData ,
} ,
// Label values with empty matchers.
{
endpoint : api . labelNames ,
params : map [ string ] string {
"name" : "foo" ,
} ,
query : url . Values {
"match[]" : [ ] string { ` { foo=""} ` } ,
} ,
errType : errorBadData ,
} ,
// Label names with matcher.
{
endpoint : api . labelNames ,
query : url . Values {
"match[]" : [ ] string { ` test_metric2 ` } ,
} ,
response : [ ] string { "__name__" , "foo" } ,
} ,
// Label names with matcher.
{
endpoint : api . labelNames ,
query : url . Values {
"match[]" : [ ] string { ` test_metric3 ` } ,
} ,
response : [ ] string { "__name__" , "dup" , "foo" } ,
} ,
// Label names with matcher using label filter.
// There is no matching series.
{
endpoint : api . labelNames ,
query : url . Values {
"match[]" : [ ] string { ` test_metric1 { foo="test"} ` } ,
} ,
response : [ ] string { } ,
} ,
// Label names with matcher and time range.
{
endpoint : api . labelNames ,
query : url . Values {
"match[]" : [ ] string { ` test_metric2 ` } ,
"start" : [ ] string { "1" } ,
"end" : [ ] string { "100000000" } ,
} ,
response : [ ] string { "__name__" , "foo" } ,
} ,
2018-06-16 10:26:37 -07:00
} ... )
}
2017-11-10 16:53:48 -08:00
methods := func ( f apiFunc ) [ ] string {
fp := reflect . ValueOf ( f ) . Pointer ( )
2019-04-02 10:00:29 -07:00
if fp == reflect . ValueOf ( api . query ) . Pointer ( ) || fp == reflect . ValueOf ( api . queryRange ) . Pointer ( ) || fp == reflect . ValueOf ( api . series ) . Pointer ( ) {
2017-11-10 16:53:48 -08:00
return [ ] string { http . MethodGet , http . MethodPost }
2015-06-08 12:19:52 -07:00
}
2017-11-10 16:53:48 -08:00
return [ ] string { http . MethodGet }
}
2015-06-08 12:19:52 -07:00
2017-11-10 16:53:48 -08:00
request := func ( m string , q url . Values ) ( * http . Request , error ) {
if m == http . MethodPost {
r , err := http . NewRequest ( m , "http://example.com" , strings . NewReader ( q . Encode ( ) ) )
r . Header . Set ( "Content-Type" , "application/x-www-form-urlencoded" )
2020-01-08 05:28:43 -08:00
r . RemoteAddr = "127.0.0.1:20201"
2017-11-10 16:53:48 -08:00
return r , err
2015-06-04 09:07:57 -07:00
}
2020-01-08 05:28:43 -08:00
r , err := http . NewRequest ( m , fmt . Sprintf ( "http://example.com?%s" , q . Encode ( ) ) , nil )
r . RemoteAddr = "127.0.0.1:20201"
return r , err
2017-11-10 16:53:48 -08:00
}
2018-06-16 10:26:37 -07:00
for i , test := range tests {
2020-07-31 08:03:02 -07:00
t . Run ( fmt . Sprintf ( "run %d %s %q" , i , describeAPIFunc ( test . endpoint ) , test . query . Encode ( ) ) , func ( t * testing . T ) {
for _ , method := range methods ( test . endpoint ) {
t . Run ( method , func ( t * testing . T ) {
// Build a context with the correct request params.
ctx := context . Background ( )
for p , v := range test . params {
ctx = route . WithParam ( ctx , p , v )
}
req , err := request ( method , test . query )
if err != nil {
t . Fatal ( err )
}
tr . ResetMetadataStore ( )
for _ , tm := range test . metadata {
tr . SetMetadataStoreForTargets ( tm . identifier , & testMetaStore { Metadata : tm . metadata } )
}
2021-03-16 02:47:45 -07:00
for _ , te := range test . exemplars {
for _ , e := range te . Exemplars {
_ , err := es . AppendExemplar ( 0 , te . SeriesLabels , e )
if err != nil {
t . Fatal ( err )
}
}
}
2020-07-31 08:03:02 -07:00
res := test . endpoint ( req . WithContext ( ctx ) )
assertAPIError ( t , res . err , test . errType )
if test . sorter != nil {
test . sorter ( res . data )
}
if test . responseLen != 0 {
assertAPIResponseLength ( t , res . data , test . responseLen )
} else {
assertAPIResponse ( t , res . data , test . response )
}
} )
2019-12-10 06:56:16 -08:00
}
2020-07-31 08:03:02 -07:00
} )
2018-11-15 05:22:16 -08:00
}
}
2018-06-27 00:15:17 -07:00
2020-07-31 08:03:02 -07:00
func describeAPIFunc ( f apiFunc ) string {
name := runtime . FuncForPC ( reflect . ValueOf ( f ) . Pointer ( ) ) . Name ( )
return strings . Split ( name [ strings . LastIndex ( name , "." ) + 1 : ] , "-" ) [ 0 ]
}
2018-11-15 05:22:16 -08:00
func assertAPIError ( t * testing . T , got * apiError , exp errorType ) {
t . Helper ( )
2018-06-27 00:15:17 -07:00
2018-11-15 05:22:16 -08:00
if got != nil {
if exp == errorNone {
t . Fatalf ( "Unexpected error: %s" , got )
}
if exp != got . typ {
t . Fatalf ( "Expected error of type %q but got type %q (%q)" , exp , got . typ , got )
2015-06-04 09:07:57 -07:00
}
2018-11-15 05:22:16 -08:00
return
}
2019-05-03 06:11:28 -07:00
if exp != errorNone {
2018-11-15 05:22:16 -08:00
t . Fatalf ( "Expected error of type %q but got none" , exp )
}
}
2021-10-22 01:06:44 -07:00
func assertAPIResponse ( t * testing . T , got , exp interface { } ) {
2019-12-09 13:36:38 -08:00
t . Helper ( )
2020-10-29 02:43:23 -07:00
require . Equal ( t , exp , got )
2015-06-04 09:07:57 -07:00
}
2019-12-10 06:56:16 -08:00
func assertAPIResponseLength ( t * testing . T , got interface { } , expLen int ) {
t . Helper ( )
gotLen := reflect . ValueOf ( got ) . Len ( )
if gotLen != expLen {
t . Fatalf (
"Response length does not match, expected:\n%d\ngot:\n%d" ,
expLen ,
gotLen ,
)
}
}
2018-11-15 05:22:16 -08:00
type fakeDB struct {
2020-04-29 09:16:14 -07:00
err error
2018-11-15 05:22:16 -08:00
}
2019-11-18 11:53:33 -08:00
func ( f * fakeDB ) CleanTombstones ( ) error { return f . err }
func ( f * fakeDB ) Delete ( mint , maxt int64 , ms ... * labels . Matcher ) error { return f . err }
2020-04-29 09:16:14 -07:00
func ( f * fakeDB ) Snapshot ( dir string , withHead bool ) error { return f . err }
2020-05-06 08:30:00 -07:00
func ( f * fakeDB ) Stats ( statsByLabelName string ) ( _ * tsdb . Stats , retErr error ) {
2022-04-27 02:24:36 -07:00
dbDir , err := os . MkdirTemp ( "" , "tsdb-api-ready" )
2020-05-06 08:30:00 -07:00
if err != nil {
return nil , err
}
defer func ( ) {
err := os . RemoveAll ( dbDir )
if retErr != nil {
retErr = err
}
} ( )
2021-02-09 06:12:48 -08:00
opts := tsdb . DefaultHeadOptions ( )
opts . ChunkRange = 1000
2021-06-05 07:29:32 -07:00
h , _ := tsdb . NewHead ( nil , nil , nil , opts , nil )
2020-04-29 09:16:14 -07:00
return h . Stats ( statsByLabelName ) , nil
2019-11-12 02:15:20 -08:00
}
2021-10-22 01:06:44 -07:00
2021-06-05 07:29:32 -07:00
func ( f * fakeDB ) WALReplayStatus ( ) ( tsdb . WALReplayStatus , error ) {
return tsdb . WALReplayStatus { } , nil
}
2018-11-15 05:22:16 -08:00
func TestAdminEndpoints ( t * testing . T ) {
2020-04-29 09:16:14 -07:00
tsdb , tsdbWithError , tsdbNotReady := & fakeDB { } , & fakeDB { err : errors . New ( "some error" ) } , & fakeDB { err : errors . Wrap ( tsdb . ErrNotReady , "wrap" ) }
2018-11-15 05:22:16 -08:00
snapshotAPI := func ( api * API ) apiFunc { return api . snapshot }
cleanAPI := func ( api * API ) apiFunc { return api . cleanTombstones }
deleteAPI := func ( api * API ) apiFunc { return api . deleteSeries }
2020-04-29 09:16:14 -07:00
for _ , tc := range [ ] struct {
2018-11-15 05:22:16 -08:00
db * fakeDB
enableAdmin bool
endpoint func ( api * API ) apiFunc
method string
values url . Values
errType errorType
} {
// Tests for the snapshot endpoint.
{
db : tsdb ,
enableAdmin : false ,
endpoint : snapshotAPI ,
errType : errorUnavailable ,
} ,
{
db : tsdb ,
enableAdmin : true ,
endpoint : snapshotAPI ,
errType : errorNone ,
} ,
{
db : tsdb ,
enableAdmin : true ,
endpoint : snapshotAPI ,
2019-01-16 14:28:08 -08:00
values : map [ string ] [ ] string { "skip_head" : { "true" } } ,
2018-11-15 05:22:16 -08:00
errType : errorNone ,
} ,
{
db : tsdb ,
enableAdmin : true ,
endpoint : snapshotAPI ,
2019-01-16 14:28:08 -08:00
values : map [ string ] [ ] string { "skip_head" : { "xxx" } } ,
2018-11-15 05:22:16 -08:00
errType : errorBadData ,
} ,
{
db : tsdbWithError ,
enableAdmin : true ,
endpoint : snapshotAPI ,
errType : errorInternal ,
} ,
{
2020-04-29 09:16:14 -07:00
db : tsdbNotReady ,
2018-11-15 05:22:16 -08:00
enableAdmin : true ,
endpoint : snapshotAPI ,
errType : errorUnavailable ,
} ,
// Tests for the cleanTombstones endpoint.
{
db : tsdb ,
enableAdmin : false ,
endpoint : cleanAPI ,
errType : errorUnavailable ,
} ,
{
db : tsdb ,
enableAdmin : true ,
endpoint : cleanAPI ,
errType : errorNone ,
} ,
{
db : tsdbWithError ,
enableAdmin : true ,
endpoint : cleanAPI ,
errType : errorInternal ,
} ,
{
2020-04-29 09:16:14 -07:00
db : tsdbNotReady ,
2018-11-15 05:22:16 -08:00
enableAdmin : true ,
endpoint : cleanAPI ,
errType : errorUnavailable ,
} ,
// Tests for the deleteSeries endpoint.
{
db : tsdb ,
enableAdmin : false ,
endpoint : deleteAPI ,
errType : errorUnavailable ,
} ,
{
db : tsdb ,
enableAdmin : true ,
endpoint : deleteAPI ,
errType : errorBadData ,
} ,
{
db : tsdb ,
enableAdmin : true ,
endpoint : deleteAPI ,
2019-01-16 14:28:08 -08:00
values : map [ string ] [ ] string { "match[]" : { "123" } } ,
2018-11-15 05:22:16 -08:00
errType : errorBadData ,
} ,
{
db : tsdb ,
enableAdmin : true ,
endpoint : deleteAPI ,
2019-01-16 14:28:08 -08:00
values : map [ string ] [ ] string { "match[]" : { "up" } , "start" : { "xxx" } } ,
2018-11-15 05:22:16 -08:00
errType : errorBadData ,
} ,
{
db : tsdb ,
enableAdmin : true ,
endpoint : deleteAPI ,
2019-01-16 14:28:08 -08:00
values : map [ string ] [ ] string { "match[]" : { "up" } , "end" : { "xxx" } } ,
2018-11-15 05:22:16 -08:00
errType : errorBadData ,
} ,
{
db : tsdb ,
enableAdmin : true ,
endpoint : deleteAPI ,
2019-01-16 14:28:08 -08:00
values : map [ string ] [ ] string { "match[]" : { "up" } } ,
2018-11-15 05:22:16 -08:00
errType : errorNone ,
} ,
{
db : tsdb ,
enableAdmin : true ,
endpoint : deleteAPI ,
2019-01-16 14:28:08 -08:00
values : map [ string ] [ ] string { "match[]" : { "up{job!=\"foo\"}" , "{job=~\"bar.+\"}" , "up{instance!~\"fred.+\"}" } } ,
2018-11-15 05:22:16 -08:00
errType : errorNone ,
} ,
{
db : tsdbWithError ,
enableAdmin : true ,
endpoint : deleteAPI ,
2019-01-16 14:28:08 -08:00
values : map [ string ] [ ] string { "match[]" : { "up" } } ,
2018-11-15 05:22:16 -08:00
errType : errorInternal ,
} ,
{
2020-04-29 09:16:14 -07:00
db : tsdbNotReady ,
2018-11-15 05:22:16 -08:00
enableAdmin : true ,
endpoint : deleteAPI ,
2020-04-29 09:16:14 -07:00
values : map [ string ] [ ] string { "match[]" : { "up" } } ,
2018-11-15 05:22:16 -08:00
errType : errorUnavailable ,
} ,
} {
tc := tc
2020-04-29 09:16:14 -07:00
t . Run ( "" , func ( t * testing . T ) {
2021-12-08 14:14:50 -08:00
dir := t . TempDir ( )
2020-04-29 09:16:14 -07:00
2018-11-15 05:22:16 -08:00
api := & API {
2020-04-29 09:16:14 -07:00
db : tc . db ,
dbDir : dir ,
2018-11-15 05:22:16 -08:00
ready : func ( f http . HandlerFunc ) http . HandlerFunc { return f } ,
enableAdmin : tc . enableAdmin ,
}
endpoint := tc . endpoint ( api )
req , err := http . NewRequest ( tc . method , fmt . Sprintf ( "?%s" , tc . values . Encode ( ) ) , nil )
2020-10-29 02:43:23 -07:00
require . NoError ( t , err )
2020-04-29 09:16:14 -07:00
res := setUnavailStatusOnTSDBNotReady ( endpoint ( req ) )
2018-11-30 06:27:12 -08:00
assertAPIError ( t , res . err , tc . errType )
2018-11-15 05:22:16 -08:00
} )
}
}
2015-06-04 09:07:57 -07:00
func TestRespondSuccess ( t * testing . T ) {
2015-07-02 01:37:19 -07:00
s := httptest . NewServer ( http . HandlerFunc ( func ( w http . ResponseWriter , r * http . Request ) {
2018-07-06 10:44:45 -07:00
api := API { }
2018-11-30 06:27:12 -08:00
api . respond ( w , "test" , nil )
2015-07-02 01:37:19 -07:00
} ) )
defer s . Close ( )
2015-06-04 09:07:57 -07:00
2015-07-02 01:37:19 -07:00
resp , err := http . Get ( s . URL )
if err != nil {
t . Fatalf ( "Error on test request: %s" , err )
2015-06-04 09:07:57 -07:00
}
2022-04-27 02:24:36 -07:00
body , err := io . ReadAll ( resp . Body )
2015-07-02 01:37:19 -07:00
defer resp . Body . Close ( )
2015-06-04 09:07:57 -07:00
if err != nil {
2015-07-02 01:37:19 -07:00
t . Fatalf ( "Error reading response body: %s" , err )
2015-06-04 09:07:57 -07:00
}
2015-07-02 01:37:19 -07:00
if resp . StatusCode != 200 {
t . Fatalf ( "Return code %d expected in success response but got %d" , 200 , resp . StatusCode )
}
if h := resp . Header . Get ( "Content-Type" ) ; h != "application/json" {
t . Fatalf ( "Expected Content-Type %q but got %q" , "application/json" , h )
}
var res response
if err = json . Unmarshal ( [ ] byte ( body ) , & res ) ; err != nil {
t . Fatalf ( "Error unmarshaling JSON body: %s" , err )
2015-06-04 09:07:57 -07:00
}
exp := & response {
Status : statusSuccess ,
Data : "test" ,
}
2020-10-29 02:43:23 -07:00
require . Equal ( t , exp , & res )
2015-06-04 09:07:57 -07:00
}
func TestRespondError ( t * testing . T ) {
2015-07-02 01:37:19 -07:00
s := httptest . NewServer ( http . HandlerFunc ( func ( w http . ResponseWriter , r * http . Request ) {
2018-07-06 10:44:45 -07:00
api := API { }
api . respondError ( w , & apiError { errorTimeout , errors . New ( "message" ) } , "test" )
2015-07-02 01:37:19 -07:00
} ) )
defer s . Close ( )
2015-06-04 09:07:57 -07:00
2015-07-02 01:37:19 -07:00
resp , err := http . Get ( s . URL )
if err != nil {
t . Fatalf ( "Error on test request: %s" , err )
2015-06-04 09:07:57 -07:00
}
2022-04-27 02:24:36 -07:00
body , err := io . ReadAll ( resp . Body )
2015-07-02 01:37:19 -07:00
defer resp . Body . Close ( )
2015-06-04 09:07:57 -07:00
if err != nil {
2015-07-02 01:37:19 -07:00
t . Fatalf ( "Error reading response body: %s" , err )
2015-06-04 09:07:57 -07:00
}
2015-11-11 14:00:54 -08:00
if want , have := http . StatusServiceUnavailable , resp . StatusCode ; want != have {
t . Fatalf ( "Return code %d expected in error response but got %d" , want , have )
2015-07-02 01:37:19 -07:00
}
if h := resp . Header . Get ( "Content-Type" ) ; h != "application/json" {
t . Fatalf ( "Expected Content-Type %q but got %q" , "application/json" , h )
}
var res response
if err = json . Unmarshal ( [ ] byte ( body ) , & res ) ; err != nil {
t . Fatalf ( "Error unmarshaling JSON body: %s" , err )
2015-06-04 09:07:57 -07:00
}
exp := & response {
Status : statusError ,
Data : "test" ,
ErrorType : errorTimeout ,
Error : "message" ,
}
2020-10-29 02:43:23 -07:00
require . Equal ( t , exp , & res )
2015-06-04 09:07:57 -07:00
}
2020-03-06 02:33:01 -08:00
func TestParseTimeParam ( t * testing . T ) {
type resultType struct {
asTime time . Time
asError func ( ) error
}
ts , err := parseTime ( "1582468023986" )
2020-10-29 02:43:23 -07:00
require . NoError ( t , err )
2020-03-06 02:33:01 -08:00
2021-10-22 01:06:44 -07:00
tests := [ ] struct {
2020-03-06 02:33:01 -08:00
paramName string
paramValue string
defaultValue time . Time
result resultType
} {
{ // When data is valid.
paramName : "start" ,
paramValue : "1582468023986" ,
defaultValue : minTime ,
result : resultType {
asTime : ts ,
asError : nil ,
} ,
} ,
{ // When data is empty string.
paramName : "end" ,
paramValue : "" ,
defaultValue : maxTime ,
result : resultType {
asTime : maxTime ,
asError : nil ,
} ,
} ,
{ // When data is not valid.
paramName : "foo" ,
paramValue : "baz" ,
defaultValue : maxTime ,
result : resultType {
asTime : time . Time { } ,
asError : func ( ) error {
_ , err := parseTime ( "baz" )
return errors . Wrapf ( err , "Invalid time value for '%s'" , "foo" )
} ,
} ,
} ,
}
for _ , test := range tests {
req , err := http . NewRequest ( "GET" , "localhost:42/foo?" + test . paramName + "=" + test . paramValue , nil )
2020-10-29 02:43:23 -07:00
require . NoError ( t , err )
2020-03-06 02:33:01 -08:00
result := test . result
asTime , err := parseTimeParam ( req , test . paramName , test . defaultValue )
if err != nil {
2020-10-29 02:43:23 -07:00
require . EqualError ( t , err , result . asError ( ) . Error ( ) )
2020-03-06 02:33:01 -08:00
} else {
2020-10-29 02:43:23 -07:00
require . True ( t , asTime . Equal ( result . asTime ) , "time as return value: %s not parsed correctly. Expected %s. Actual %s" , test . paramValue , result . asTime , asTime )
2020-03-06 02:33:01 -08:00
}
}
}
2015-06-04 09:07:57 -07:00
func TestParseTime ( t * testing . T ) {
ts , err := time . Parse ( time . RFC3339Nano , "2015-06-03T13:21:58.555Z" )
if err != nil {
panic ( err )
}
2021-10-22 01:06:44 -07:00
tests := [ ] struct {
2015-06-04 09:07:57 -07:00
input string
fail bool
result time . Time
} {
{
input : "" ,
fail : true ,
2021-10-22 01:06:44 -07:00
} ,
{
2015-06-04 09:07:57 -07:00
input : "abc" ,
fail : true ,
2021-10-22 01:06:44 -07:00
} ,
{
2015-06-04 09:07:57 -07:00
input : "30s" ,
fail : true ,
2021-10-22 01:06:44 -07:00
} ,
{
2015-06-04 09:07:57 -07:00
input : "123" ,
result : time . Unix ( 123 , 0 ) ,
2021-10-22 01:06:44 -07:00
} ,
{
2015-06-04 09:07:57 -07:00
input : "123.123" ,
result : time . Unix ( 123 , 123000000 ) ,
2021-10-22 01:06:44 -07:00
} ,
{
2015-06-04 09:07:57 -07:00
input : "2015-06-03T13:21:58.555Z" ,
result : ts ,
2021-10-22 01:06:44 -07:00
} ,
{
2015-06-04 09:07:57 -07:00
input : "2015-06-03T14:21:58.555+01:00" ,
result : ts ,
2021-10-22 01:06:44 -07:00
} ,
{
2018-12-03 04:25:54 -08:00
// Test float rounding.
input : "1543578564.705" ,
result : time . Unix ( 1543578564 , 705 * 1e6 ) ,
2015-06-04 09:07:57 -07:00
} ,
2019-07-08 02:43:59 -07:00
{
input : minTime . Format ( time . RFC3339Nano ) ,
result : minTime ,
} ,
{
input : maxTime . Format ( time . RFC3339Nano ) ,
result : maxTime ,
} ,
2015-06-04 09:07:57 -07:00
}
for _ , test := range tests {
ts , err := parseTime ( test . input )
if err != nil && ! test . fail {
t . Errorf ( "Unexpected error for %q: %s" , test . input , err )
continue
}
if err == nil && test . fail {
t . Errorf ( "Expected error for %q but got none" , test . input )
continue
}
2016-12-30 01:43:44 -08:00
if ! test . fail && ! ts . Equal ( test . result ) {
t . Errorf ( "Expected time %v for input %q but got %v" , test . result , test . input , ts )
2015-06-04 09:07:57 -07:00
}
}
}
func TestParseDuration ( t * testing . T ) {
2021-10-22 01:06:44 -07:00
tests := [ ] struct {
2015-06-04 09:07:57 -07:00
input string
fail bool
result time . Duration
} {
{
input : "" ,
fail : true ,
} , {
input : "abc" ,
fail : true ,
} , {
input : "2015-06-03T13:21:58.555Z" ,
fail : true ,
2017-03-16 07:16:20 -07:00
} , {
// Internal int64 overflow.
input : "-148966367200.372" ,
fail : true ,
} , {
// Internal int64 overflow.
input : "148966367200.372" ,
fail : true ,
2015-06-04 09:07:57 -07:00
} , {
input : "123" ,
result : 123 * time . Second ,
} , {
input : "123.333" ,
result : 123 * time . Second + 333 * time . Millisecond ,
} , {
input : "15s" ,
result : 15 * time . Second ,
} , {
input : "5m" ,
result : 5 * time . Minute ,
} ,
}
for _ , test := range tests {
d , err := parseDuration ( test . input )
if err != nil && ! test . fail {
t . Errorf ( "Unexpected error for %q: %s" , test . input , err )
continue
}
if err == nil && test . fail {
t . Errorf ( "Expected error for %q but got none" , test . input )
continue
}
if ! test . fail && d != test . result {
t . Errorf ( "Expected duration %v for input %q but got %v" , test . result , test . input , d )
}
}
}
2016-01-25 16:32:46 -08:00
func TestOptionsMethod ( t * testing . T ) {
2017-05-02 16:49:29 -07:00
r := route . New ( )
2017-10-06 08:20:20 -07:00
api := & API { ready : func ( f http . HandlerFunc ) http . HandlerFunc { return f } }
2016-01-25 16:32:46 -08:00
api . Register ( r )
s := httptest . NewServer ( r )
defer s . Close ( )
req , err := http . NewRequest ( "OPTIONS" , s . URL + "/any_path" , nil )
if err != nil {
t . Fatalf ( "Error creating OPTIONS request: %s" , err )
}
client := & http . Client { }
resp , err := client . Do ( req )
if err != nil {
t . Fatalf ( "Error executing OPTIONS request: %s" , err )
}
if resp . StatusCode != http . StatusNoContent {
t . Fatalf ( "Expected status %d, got %d" , http . StatusNoContent , resp . StatusCode )
}
}
2018-02-07 07:40:36 -08:00
2018-02-08 09:28:55 -08:00
func TestRespond ( t * testing . T ) {
cases := [ ] struct {
response interface { }
expected string
} {
{
response : & queryData {
2020-02-03 10:23:07 -08:00
ResultType : parser . ValueTypeMatrix ,
2018-02-08 09:28:55 -08:00
Result : promql . Matrix {
promql . Series {
Points : [ ] promql . Point { { V : 1 , T : 1000 } } ,
Metric : labels . FromStrings ( "__name__" , "foo" ) ,
} ,
} ,
} ,
expected : ` { "status":"success","data": { "resultType":"matrix","result":[ { "metric": { "__name__":"foo"},"values":[[1,"1"]]}]}} ` ,
} ,
{
response : promql . Point { V : 0 , T : 0 } ,
expected : ` { "status":"success","data":[0,"0"]} ` ,
} ,
{
response : promql . Point { V : 20 , T : 1 } ,
expected : ` { "status":"success","data":[0.001,"20"]} ` ,
} ,
{
response : promql . Point { V : 20 , T : 10 } ,
2018-02-07 04:27:57 -08:00
expected : ` { "status":"success","data":[0.010,"20"]} ` ,
2018-02-08 09:28:55 -08:00
} ,
{
response : promql . Point { V : 20 , T : 100 } ,
2018-02-07 04:27:57 -08:00
expected : ` { "status":"success","data":[0.100,"20"]} ` ,
2018-02-08 09:28:55 -08:00
} ,
{
response : promql . Point { V : 20 , T : 1001 } ,
expected : ` { "status":"success","data":[1.001,"20"]} ` ,
} ,
{
response : promql . Point { V : 20 , T : 1010 } ,
2018-02-07 04:27:57 -08:00
expected : ` { "status":"success","data":[1.010,"20"]} ` ,
2018-02-08 09:28:55 -08:00
} ,
{
response : promql . Point { V : 20 , T : 1100 } ,
2018-02-07 04:27:57 -08:00
expected : ` { "status":"success","data":[1.100,"20"]} ` ,
2018-02-08 09:28:55 -08:00
} ,
{
response : promql . Point { V : 20 , T : 12345678123456555 } ,
2018-02-07 04:27:57 -08:00
expected : ` { "status":"success","data":[12345678123456.555,"20"]} ` ,
2018-02-08 09:28:55 -08:00
} ,
{
response : promql . Point { V : 20 , T : - 1 } ,
expected : ` { "status":"success","data":[-0.001,"20"]} ` ,
} ,
{
response : promql . Point { V : math . NaN ( ) , T : 0 } ,
expected : ` { "status":"success","data":[0,"NaN"]} ` ,
} ,
{
response : promql . Point { V : math . Inf ( 1 ) , T : 0 } ,
expected : ` { "status":"success","data":[0,"+Inf"]} ` ,
} ,
{
response : promql . Point { V : math . Inf ( - 1 ) , T : 0 } ,
expected : ` { "status":"success","data":[0,"-Inf"]} ` ,
} ,
{
response : promql . Point { V : 1.2345678e6 , T : 0 } ,
expected : ` { "status":"success","data":[0,"1234567.8"]} ` ,
} ,
{
response : promql . Point { V : 1.2345678e-6 , T : 0 } ,
expected : ` { "status":"success","data":[0,"0.0000012345678"]} ` ,
} ,
{
response : promql . Point { V : 1.2345678e-67 , T : 0 } ,
expected : ` { "status":"success","data":[0,"1.2345678e-67"]} ` ,
} ,
2021-03-16 02:47:45 -07:00
{
response : [ ] exemplar . QueryResult {
{
SeriesLabels : labels . FromStrings ( "foo" , "bar" ) ,
Exemplars : [ ] exemplar . Exemplar {
{
Labels : labels . FromStrings ( "traceID" , "abc" ) ,
Value : 100.123 ,
Ts : 1234 ,
} ,
} ,
} ,
} ,
expected : ` { "status":"success","data":[ { "seriesLabels": { "foo":"bar"},"exemplars":[ { "labels": { "traceID":"abc"},"value":"100.123","timestamp":1.234}]}]} ` ,
} ,
{
response : [ ] exemplar . QueryResult {
{
SeriesLabels : labels . FromStrings ( "foo" , "bar" ) ,
Exemplars : [ ] exemplar . Exemplar {
{
Labels : labels . FromStrings ( "traceID" , "abc" ) ,
Value : math . Inf ( 1 ) ,
Ts : 1234 ,
} ,
} ,
} ,
} ,
expected : ` { "status":"success","data":[ { "seriesLabels": { "foo":"bar"},"exemplars":[ { "labels": { "traceID":"abc"},"value":"+Inf","timestamp":1.234}]}]} ` ,
} ,
2018-02-08 09:28:55 -08:00
}
for _ , c := range cases {
s := httptest . NewServer ( http . HandlerFunc ( func ( w http . ResponseWriter , r * http . Request ) {
2018-07-06 10:44:45 -07:00
api := API { }
2018-11-30 06:27:12 -08:00
api . respond ( w , c . response , nil )
2018-02-08 09:28:55 -08:00
} ) )
defer s . Close ( )
resp , err := http . Get ( s . URL )
if err != nil {
t . Fatalf ( "Error on test request: %s" , err )
}
2022-04-27 02:24:36 -07:00
body , err := io . ReadAll ( resp . Body )
2018-02-08 09:28:55 -08:00
defer resp . Body . Close ( )
if err != nil {
t . Fatalf ( "Error reading response body: %s" , err )
}
if string ( body ) != c . expected {
t . Fatalf ( "Expected response \n%v\n but got \n%v\n" , c . expected , string ( body ) )
}
}
}
2019-11-12 02:15:20 -08:00
func TestTSDBStatus ( t * testing . T ) {
tsdb := & fakeDB { }
tsdbStatusAPI := func ( api * API ) apiFunc { return api . serveTSDBStatus }
for i , tc := range [ ] struct {
db * fakeDB
endpoint func ( api * API ) apiFunc
method string
values url . Values
errType errorType
} {
// Tests for the TSDB Status endpoint.
{
db : tsdb ,
endpoint : tsdbStatusAPI ,
errType : errorNone ,
} ,
} {
tc := tc
t . Run ( fmt . Sprintf ( "%d" , i ) , func ( t * testing . T ) {
2020-09-29 13:05:33 -07:00
api := & API { db : tc . db , gatherer : prometheus . DefaultGatherer }
2019-11-12 02:15:20 -08:00
endpoint := tc . endpoint ( api )
req , err := http . NewRequest ( tc . method , fmt . Sprintf ( "?%s" , tc . values . Encode ( ) ) , nil )
if err != nil {
t . Fatalf ( "Error when creating test request: %s" , err )
}
res := endpoint ( req )
assertAPIError ( t , res . err , tc . errType )
} )
}
}
2020-06-22 07:29:35 -07:00
func TestReturnAPIError ( t * testing . T ) {
cases := [ ] struct {
err error
expected errorType
} {
{
err : promql . ErrStorage { Err : errors . New ( "storage error" ) } ,
expected : errorInternal ,
} , {
2022-06-13 08:45:35 -07:00
err : fmt . Errorf ( "wrapped: %w" , promql . ErrStorage { Err : errors . New ( "storage error" ) } ) ,
2020-06-22 07:29:35 -07:00
expected : errorInternal ,
} , {
err : promql . ErrQueryTimeout ( "timeout error" ) ,
expected : errorTimeout ,
} , {
2022-06-13 08:45:35 -07:00
err : fmt . Errorf ( "wrapped: %w" , promql . ErrQueryTimeout ( "timeout error" ) ) ,
2020-06-22 07:29:35 -07:00
expected : errorTimeout ,
} , {
err : promql . ErrQueryCanceled ( "canceled error" ) ,
expected : errorCanceled ,
} , {
2022-06-13 08:45:35 -07:00
err : fmt . Errorf ( "wrapped: %w" , promql . ErrQueryCanceled ( "canceled error" ) ) ,
2020-06-22 07:29:35 -07:00
expected : errorCanceled ,
} , {
err : errors . New ( "exec error" ) ,
expected : errorExec ,
} ,
}
2022-06-13 08:45:35 -07:00
for ix , c := range cases {
2020-06-22 07:29:35 -07:00
actual := returnAPIError ( c . err )
2022-06-13 08:45:35 -07:00
require . Error ( t , actual , ix )
require . Equal ( t , c . expected , actual . typ , ix )
2020-06-22 07:29:35 -07:00
}
}
2018-02-07 07:40:36 -08:00
// This is a global to avoid the benchmark being optimized away.
var testResponseWriter = httptest . ResponseRecorder { }
func BenchmarkRespond ( b * testing . B ) {
b . ReportAllocs ( )
points := [ ] promql . Point { }
for i := 0 ; i < 10000 ; i ++ {
points = append ( points , promql . Point { V : float64 ( i * 1000000 ) , T : int64 ( i ) } )
}
response := & queryData {
2020-02-03 10:23:07 -08:00
ResultType : parser . ValueTypeMatrix ,
2018-02-07 07:40:36 -08:00
Result : promql . Matrix {
promql . Series {
Points : points ,
Metric : nil ,
} ,
} ,
}
2018-02-08 09:28:55 -08:00
b . ResetTimer ( )
2018-07-06 10:44:45 -07:00
api := API { }
2018-02-07 07:40:36 -08:00
for n := 0 ; n < b . N ; n ++ {
2018-11-30 06:27:12 -08:00
api . respond ( & testResponseWriter , response , nil )
2018-02-07 07:40:36 -08:00
}
}
2021-02-05 03:45:44 -08:00
func TestGetGlobalURL ( t * testing . T ) {
mustParseURL := func ( t * testing . T , u string ) * url . URL {
parsed , err := url . Parse ( u )
require . NoError ( t , err )
return parsed
}
testcases := [ ] struct {
input * url . URL
opts GlobalURLOptions
expected * url . URL
errorful bool
} {
{
mustParseURL ( t , "http://127.0.0.1:9090" ) ,
GlobalURLOptions {
ListenAddress : "127.0.0.1:9090" ,
Host : "127.0.0.1:9090" ,
Scheme : "http" ,
} ,
mustParseURL ( t , "http://127.0.0.1:9090" ) ,
false ,
} ,
{
mustParseURL ( t , "http://127.0.0.1:9090" ) ,
GlobalURLOptions {
ListenAddress : "127.0.0.1:9090" ,
Host : "prometheus.io" ,
Scheme : "https" ,
} ,
mustParseURL ( t , "https://prometheus.io" ) ,
false ,
} ,
{
mustParseURL ( t , "http://exemple.com" ) ,
GlobalURLOptions {
ListenAddress : "127.0.0.1:9090" ,
Host : "prometheus.io" ,
Scheme : "https" ,
} ,
mustParseURL ( t , "http://exemple.com" ) ,
false ,
} ,
{
mustParseURL ( t , "http://localhost:8080" ) ,
GlobalURLOptions {
ListenAddress : "127.0.0.1:9090" ,
Host : "prometheus.io" ,
Scheme : "https" ,
} ,
mustParseURL ( t , "http://prometheus.io:8080" ) ,
false ,
} ,
{
mustParseURL ( t , "http://[::1]:8080" ) ,
GlobalURLOptions {
ListenAddress : "127.0.0.1:9090" ,
Host : "prometheus.io" ,
Scheme : "https" ,
} ,
mustParseURL ( t , "http://prometheus.io:8080" ) ,
false ,
} ,
{
mustParseURL ( t , "http://localhost" ) ,
GlobalURLOptions {
ListenAddress : "127.0.0.1:9090" ,
Host : "prometheus.io" ,
Scheme : "https" ,
} ,
mustParseURL ( t , "http://prometheus.io" ) ,
false ,
} ,
{
mustParseURL ( t , "http://localhost:9091" ) ,
GlobalURLOptions {
ListenAddress : "[::1]:9090" ,
Host : "[::1]" ,
Scheme : "https" ,
} ,
mustParseURL ( t , "http://[::1]:9091" ) ,
false ,
} ,
{
mustParseURL ( t , "http://localhost:9091" ) ,
GlobalURLOptions {
ListenAddress : "[::1]:9090" ,
Host : "[::1]:9090" ,
Scheme : "https" ,
} ,
mustParseURL ( t , "http://[::1]:9091" ) ,
false ,
} ,
}
for i , tc := range testcases {
t . Run ( fmt . Sprintf ( "Test %d" , i ) , func ( t * testing . T ) {
output , err := getGlobalURL ( tc . input , tc . opts )
if tc . errorful {
require . Error ( t , err )
return
}
require . NoError ( t , err )
require . Equal ( t , tc . expected , output )
} )
}
}