func (serv MetricsService) SetTargets(targetGroups []TargetGroup, jobName string) { if job := serv.appState.Config.GetJobByName(jobName); job == nil { rb := serv.ResponseBuilder() rb.SetResponseCode(http.StatusNotFound) } else { newTargets := []retrieval.Target{} for _, targetGroup := range targetGroups { // Do mandatory map type conversion due to Go shortcomings. baseLabels := model.LabelSet{} for label, value := range targetGroup.BaseLabels { baseLabels[model.LabelName(label)] = model.LabelValue(value) } for _, endpoint := range targetGroup.Endpoints { newTarget := retrieval.NewTarget(endpoint, time.Second*5, baseLabels) newTargets = append(newTargets, newTarget) } } serv.appState.TargetManager.ReplaceTargets(job, newTargets, serv.appState.Config.Global.ScrapeInterval) } }
// SetTargets handles the /api/targets endpoint. func (serv MetricsService) SetTargets(w http.ResponseWriter, r *http.Request) { params := httputils.GetQueryParams(r) jobName := params.Get("job") decoder := json.NewDecoder(r.Body) var targetGroups []TargetGroup err := decoder.Decode(&targetGroups) if err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } job := serv.Config.GetJobByName(jobName) if job == nil { http.Error(w, "job not found", http.StatusNotFound) return } newTargets := []retrieval.Target{} for _, targetGroup := range targetGroups { // Do mandatory map type conversion due to Go shortcomings. baseLabels := clientmodel.LabelSet{ clientmodel.JobLabel: clientmodel.LabelValue(job.GetName()), } for label, value := range targetGroup.BaseLabels { baseLabels[clientmodel.LabelName(label)] = clientmodel.LabelValue(value) } for _, endpoint := range targetGroup.Endpoints { newTarget := retrieval.NewTarget(endpoint, job.ScrapeTimeout(), baseLabels) newTargets = append(newTargets, newTarget) } } // BUG(julius): Validate that this ScrapeInterval is in fact the proper one // for the job. serv.TargetManager.ReplaceTargets(*job, newTargets) }
func TestEndpoints(t *testing.T) { suite, err := promql.NewTest(t, ` load 1m test_metric1{foo="bar"} 0+100x100 test_metric1{foo="boo"} 1+0x100 test_metric2{foo="boo"} 1+0x100 `) if err != nil { t.Fatal(err) } defer suite.Close() if err := suite.Run(); err != nil { t.Fatal(err) } now := model.Now() tr := targetRetrieverFunc(func() []retrieval.Target { return []retrieval.Target{ *retrieval.NewTarget( model.LabelSet{ model.SchemeLabel: "http", model.AddressLabel: "example.com:8080", model.MetricsPathLabel: "/metrics", }, model.LabelSet{}, url.Values{}, ), } }) api := &API{ Storage: suite.Storage(), QueryEngine: suite.QueryEngine(), targetRetriever: tr, now: func() model.Time { return now }, } start := model.Time(0) var tests = []struct { endpoint apiFunc params map[string]string query url.Values response interface{} errType errorType }{ { endpoint: api.query, query: url.Values{ "query": []string{"2"}, "time": []string{"123.3"}, }, response: &queryData{ ResultType: model.ValScalar, Result: &model.Scalar{ Value: 2, Timestamp: start.Add(123*time.Second + 300*time.Millisecond), }, }, }, { endpoint: api.query, query: url.Values{ "query": []string{"0.333"}, "time": []string{"1970-01-01T00:02:03Z"}, }, response: &queryData{ ResultType: model.ValScalar, Result: &model.Scalar{ Value: 0.333, Timestamp: start.Add(123 * time.Second), }, }, }, { endpoint: api.query, query: url.Values{ "query": []string{"0.333"}, "time": []string{"1970-01-01T01:02:03+01:00"}, }, response: &queryData{ ResultType: model.ValScalar, Result: &model.Scalar{ Value: 0.333, Timestamp: start.Add(123 * time.Second), }, }, }, { endpoint: api.query, query: url.Values{ "query": []string{"0.333"}, }, response: &queryData{ ResultType: model.ValScalar, Result: &model.Scalar{ Value: 0.333, Timestamp: now, }, }, }, { endpoint: api.queryRange, query: url.Values{ "query": []string{"time()"}, "start": []string{"0"}, "end": []string{"2"}, "step": []string{"1"}, }, response: &queryData{ ResultType: model.ValMatrix, Result: model.Matrix{ &model.SampleStream{ Values: []model.SamplePair{ {Value: 0, Timestamp: start}, {Value: 1, Timestamp: start.Add(1 * time.Second)}, {Value: 2, Timestamp: start.Add(2 * time.Second)}, }, Metric: model.Metric{}, }, }, }, }, // 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, }, // Invalid step { endpoint: api.queryRange, query: url.Values{ "query": []string{"time()"}, "start": []string{"1"}, "end": []string{"2"}, "step": []string{"0"}, }, errType: errorBadData, }, // Start after end { endpoint: api.queryRange, query: url.Values{ "query": []string{"time()"}, "start": []string{"2"}, "end": []string{"1"}, "step": []string{"1"}, }, errType: errorBadData, }, { endpoint: api.labelValues, params: map[string]string{ "name": "__name__", }, response: model.LabelValues{ "test_metric1", "test_metric2", }, }, { endpoint: api.labelValues, params: map[string]string{ "name": "foo", }, response: model.LabelValues{ "bar", "boo", }, }, // Bad name parameter. { endpoint: api.labelValues, params: map[string]string{ "name": "not!!!allowed", }, errType: errorBadData, }, { endpoint: api.series, query: url.Values{ "match[]": []string{`test_metric2`}, }, response: []model.Metric{ { "__name__": "test_metric2", "foo": "boo", }, }, }, { endpoint: api.series, query: url.Values{ "match[]": []string{`test_metric1{foo=~".+o"}`}, }, response: []model.Metric{ { "__name__": "test_metric1", "foo": "boo", }, }, }, { endpoint: api.series, query: url.Values{ "match[]": []string{`test_metric1{foo=~"o$"}`, `test_metric1{foo=~".+o"}`}, }, response: []model.Metric{ { "__name__": "test_metric1", "foo": "boo", }, }, }, { endpoint: api.series, query: url.Values{ "match[]": []string{`test_metric1{foo=~".+o"}`, `none`}, }, response: []model.Metric{ { "__name__": "test_metric1", "foo": "boo", }, }, }, // Start and end before series starts. { endpoint: api.series, query: url.Values{ "match[]": []string{`test_metric2`}, "start": []string{"-2"}, "end": []string{"-1"}, }, response: []model.Metric{}, }, // Start and end after series ends. { endpoint: api.series, query: url.Values{ "match[]": []string{`test_metric2`}, "start": []string{"100000"}, "end": []string{"100001"}, }, response: []model.Metric{}, }, // Start before series starts, end after series ends. { endpoint: api.series, query: url.Values{ "match[]": []string{`test_metric2`}, "start": []string{"-1"}, "end": []string{"100000"}, }, response: []model.Metric{ { "__name__": "test_metric2", "foo": "boo", }, }, }, // Start and end within series. { endpoint: api.series, query: url.Values{ "match[]": []string{`test_metric2`}, "start": []string{"1"}, "end": []string{"100"}, }, response: []model.Metric{ { "__name__": "test_metric2", "foo": "boo", }, }, }, // Start within series, end after. { endpoint: api.series, query: url.Values{ "match[]": []string{`test_metric2`}, "start": []string{"1"}, "end": []string{"100000"}, }, response: []model.Metric{ { "__name__": "test_metric2", "foo": "boo", }, }, }, // Start before series, end within series. { endpoint: api.series, query: url.Values{ "match[]": []string{`test_metric2`}, "start": []string{"-1"}, "end": []string{"1"}, }, response: []model.Metric{ { "__name__": "test_metric2", "foo": "boo", }, }, }, // Missing match[] query params in series requests. { endpoint: api.series, errType: errorBadData, }, { endpoint: api.dropSeries, errType: errorBadData, }, // The following tests delete time series from the test storage. They // must remain at the end and are fixed in their order. { endpoint: api.dropSeries, query: url.Values{ "match[]": []string{`test_metric1{foo=~".+o"}`}, }, response: struct { NumDeleted int `json:"numDeleted"` }{1}, }, { endpoint: api.series, query: url.Values{ "match[]": []string{`test_metric1`}, }, response: []model.Metric{ { "__name__": "test_metric1", "foo": "bar", }, }, }, { endpoint: api.dropSeries, query: url.Values{ "match[]": []string{`{__name__=~".+"}`}, }, response: struct { NumDeleted int `json:"numDeleted"` }{2}, }, { endpoint: api.targets, response: []*Target{ &Target{ DiscoveredLabels: model.LabelSet{}, Labels: model.LabelSet{}, ScrapeUrl: "http://example.com:8080/metrics", Health: "unknown", }, }, }, } for _, test := range tests { // Build a context with the correct request params. ctx := context.Background() for p, v := range test.params { ctx = route.WithParam(ctx, p, v) } api.context = func(r *http.Request) context.Context { return ctx } req, err := http.NewRequest("ANY", fmt.Sprintf("http://example.com?%s", test.query.Encode()), nil) if err != nil { t.Fatal(err) } resp, apiErr := test.endpoint(req) if apiErr != nil { if test.errType == errorNone { t.Fatalf("Unexpected error: %s", apiErr) } if test.errType != apiErr.typ { t.Fatalf("Expected error of type %q but got type %q", test.errType, apiErr.typ) } continue } if apiErr == nil && test.errType != errorNone { t.Fatalf("Expected error of type %q but got none", test.errType) } if !reflect.DeepEqual(resp, test.response) { t.Fatalf("Response does not match, expected:\n%+v\ngot:\n%+v", test.response, resp) } // Ensure that removed metrics are unindexed before the next request. suite.Storage().WaitForIndexing() } }