Skip to content

Commit 133d1cf

Browse files
committed
Implement matching objectives with query on ListObjectives
1 parent 8997a8b commit 133d1cf

File tree

3 files changed

+133
-16
lines changed

3 files changed

+133
-16
lines changed

filesystem.go

Lines changed: 68 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,24 +7,63 @@ import (
77
"log"
88
"net/http"
99
"path/filepath"
10+
"sync"
1011

1112
"github.com/fsnotify/fsnotify"
1213
"github.com/oklog/run"
1314
monitoringv1 "github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1"
1415
"github.com/prometheus/client_golang/prometheus"
1516
"github.com/prometheus/client_golang/prometheus/promhttp"
17+
"github.com/prometheus/prometheus/pkg/labels"
18+
"github.com/prometheus/prometheus/promql/parser"
1619
"github.com/pyrra-dev/pyrra/kubernetes/api/v1alpha1"
1720
"github.com/pyrra-dev/pyrra/openapi"
1821
openapiserver "github.com/pyrra-dev/pyrra/openapi/server/go"
1922
"github.com/pyrra-dev/pyrra/slo"
2023
"sigs.k8s.io/yaml"
2124
)
2225

23-
var objectives = map[string]slo.Objective{}
26+
type Objectives struct {
27+
mu sync.RWMutex
28+
objectives map[string]slo.Objective
29+
}
2430

25-
func cmdFilesystem(configFiles, prometheusFolder string) {
26-
var gr run.Group
31+
func (os *Objectives) Set(o slo.Objective) {
32+
os.mu.Lock()
33+
os.objectives[o.Labels.String()] = o
34+
os.mu.Unlock()
35+
}
36+
37+
func (os *Objectives) Match(ms []*labels.Matcher) []slo.Objective {
38+
if ms == nil || len(ms) == 0 {
39+
os.mu.RLock()
40+
objectives := make([]slo.Objective, 0, len(os.objectives))
41+
for _, o := range os.objectives {
42+
objectives = append(objectives, o)
43+
}
44+
os.mu.RUnlock()
45+
return objectives
46+
}
47+
48+
os.mu.RLock()
49+
defer os.mu.RUnlock()
50+
51+
var objectives []slo.Objective
2752

53+
for _, o := range os.objectives {
54+
for _, m := range ms {
55+
v := o.Labels.Get(m.Name)
56+
if !m.Matches(v) {
57+
continue
58+
}
59+
objectives = append(objectives, o)
60+
}
61+
}
62+
63+
return objectives
64+
}
65+
66+
func cmdFilesystem(configFiles, prometheusFolder string) {
2867
reg := prometheus.NewRegistry()
2968

3069
reconcilesTotal := prometheus.NewCounter(prometheus.CounterOpts{
@@ -43,8 +82,10 @@ func cmdFilesystem(configFiles, prometheusFolder string) {
4382
)
4483

4584
ctx, cancel := context.WithCancel(context.Background())
85+
objectives := &Objectives{objectives: map[string]slo.Objective{}}
4686
files := make(chan string, 16)
4787

88+
var gr run.Group
4889
{
4990
gr.Add(func() error {
5091
// Initially read all files and send them to be processed and added to the in memory store.
@@ -143,7 +184,7 @@ func cmdFilesystem(configFiles, prometheusFolder string) {
143184
return err
144185
}
145186

146-
objectives[objective.Labels.String()] = objective
187+
objectives.Set(objective)
147188
}
148189
}
149190
}, func(err error) {
@@ -152,7 +193,9 @@ func cmdFilesystem(configFiles, prometheusFolder string) {
152193
}
153194
{
154195
router := openapiserver.NewRouter(
155-
openapiserver.NewObjectivesApiController(&FilesystemObjectiveServer{}),
196+
openapiserver.NewObjectivesApiController(&FilesystemObjectiveServer{
197+
objectives: objectives,
198+
}),
156199
)
157200
router.Handle("/metrics", promhttp.HandlerFor(reg, promhttp.HandlerOpts{}))
158201

@@ -172,12 +215,24 @@ func cmdFilesystem(configFiles, prometheusFolder string) {
172215
}
173216
}
174217

175-
type FilesystemObjectiveServer struct{}
218+
type FilesystemObjectiveServer struct {
219+
objectives *Objectives
220+
}
176221

177-
func (f FilesystemObjectiveServer) ListObjectives(ctx context.Context) (openapiserver.ImplResponse, error) {
222+
func (f FilesystemObjectiveServer) ListObjectives(ctx context.Context, query string) (openapiserver.ImplResponse, error) {
223+
var matchers []*labels.Matcher
224+
if query != "" {
225+
var err error
226+
matchers, err = parser.ParseMetricSelector(query)
227+
if err != nil {
228+
return openapiserver.ImplResponse{Code: http.StatusBadRequest}, err
229+
}
230+
}
231+
232+
objectives := f.objectives.Match(matchers)
178233
list := make([]openapiserver.Objective, 0, len(objectives))
179-
for _, objective := range objectives {
180-
list = append(list, openapi.ServerFromInternal(objective))
234+
for _, o := range objectives {
235+
list = append(list, openapi.ServerFromInternal(o))
181236
}
182237

183238
return openapiserver.ImplResponse{
@@ -188,7 +243,10 @@ func (f FilesystemObjectiveServer) ListObjectives(ctx context.Context) (openapis
188243

189244
func (f FilesystemObjectiveServer) GetObjective(ctx context.Context, expr string) (openapiserver.ImplResponse, error) {
190245
// TODO: Parse expr to match properly
191-
objective, ok := objectives[expr]
246+
247+
f.objectives.mu.RLock()
248+
objective, ok := f.objectives.objectives[expr]
249+
f.objectives.mu.RUnlock()
192250
if !ok {
193251
return openapiserver.ImplResponse{Code: http.StatusNotFound}, nil
194252
}

filesystem_test.go

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package main
2+
3+
import (
4+
"testing"
5+
6+
"github.com/prometheus/prometheus/pkg/labels"
7+
"github.com/pyrra-dev/pyrra/slo"
8+
"github.com/stretchr/testify/require"
9+
)
10+
11+
func TestMatchObjectives(t *testing.T) {
12+
o1 := slo.Objective{Labels: labels.FromStrings("foo", "bar")}
13+
o2 := slo.Objective{Labels: labels.FromStrings("foo", "bar", "ying", "yang")}
14+
o3 := slo.Objective{Labels: labels.FromStrings("foo", "baz")}
15+
16+
objectives := Objectives{objectives: map[string]slo.Objective{}}
17+
objectives.Set(o1)
18+
objectives.Set(o2)
19+
objectives.Set(o3)
20+
21+
matches := objectives.Match([]*labels.Matcher{
22+
labels.MustNewMatcher(labels.MatchEqual, "foo", "foo"),
23+
})
24+
require.Nil(t, matches)
25+
26+
matches = objectives.Match([]*labels.Matcher{
27+
labels.MustNewMatcher(labels.MatchEqual, "foo", "bar"),
28+
})
29+
require.Contains(t, matches, o1)
30+
require.Contains(t, matches, o2)
31+
32+
matches = objectives.Match([]*labels.Matcher{
33+
labels.MustNewMatcher(labels.MatchEqual, "foo", "baz"),
34+
})
35+
require.Contains(t, matches, o3)
36+
37+
matches = objectives.Match([]*labels.Matcher{
38+
labels.MustNewMatcher(labels.MatchEqual, "foo", "bar"),
39+
labels.MustNewMatcher(labels.MatchEqual, "ying", "yang"),
40+
})
41+
require.Contains(t, matches, o2)
42+
43+
matches = objectives.Match([]*labels.Matcher{
44+
labels.MustNewMatcher(labels.MatchRegexp, "foo", "ba."),
45+
})
46+
require.Contains(t, matches, o1)
47+
require.Contains(t, matches, o2)
48+
require.Contains(t, matches, o3)
49+
}

main.go

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import (
2929
promconfig "github.com/prometheus/common/config"
3030
"github.com/prometheus/common/model"
3131
"github.com/prometheus/prometheus/pkg/labels"
32+
"github.com/prometheus/prometheus/promql/parser"
3233
"github.com/pyrra-dev/pyrra/openapi"
3334
openapiclient "github.com/pyrra-dev/pyrra/openapi/client"
3435
openapiserver "github.com/pyrra-dev/pyrra/openapi/server/go"
@@ -63,8 +64,6 @@ func main() {
6364
case "kubernetes":
6465
cmdKubernetes(CLI.Kubernetes.MetricsAddr)
6566
}
66-
67-
return
6867
}
6968

7069
func cmdAPI(prometheusURL, prometheusExternal, apiURL *url.URL, prometheusBearerTokenPath string) {
@@ -279,8 +278,15 @@ type ObjectivesServer struct {
279278
apiclient *openapiclient.APIClient
280279
}
281280

282-
func (o *ObjectivesServer) ListObjectives(ctx context.Context) (openapiserver.ImplResponse, error) {
283-
objectives, _, err := o.apiclient.ObjectivesApi.ListObjectives(ctx).Execute()
281+
func (o *ObjectivesServer) ListObjectives(ctx context.Context, query string) (openapiserver.ImplResponse, error) {
282+
if query != "" {
283+
// We'll parse the query matchers already to make sure it's valid before passing on to the backend.
284+
if _, err := parser.ParseMetricSelector(query); err != nil {
285+
return openapiserver.ImplResponse{Code: http.StatusBadRequest}, err
286+
}
287+
}
288+
289+
objectives, _, err := o.apiclient.ObjectivesApi.ListObjectives(ctx, query).Execute()
284290
if err != nil {
285291
return openapiserver.ImplResponse{Code: http.StatusInternalServerError}, err
286292
}
@@ -315,7 +321,7 @@ func (o *ObjectivesServer) GetObjective(ctx context.Context, expr string) (opena
315321
}
316322

317323
func (o *ObjectivesServer) GetObjectiveStatus(ctx context.Context, expr string) (openapiserver.ImplResponse, error) {
318-
clientObjective, _, err := o.apiclient.ObjectivesApi.GetObjective(ctx, expr).Execute()
324+
clientObjectives, _, err := o.apiclient.ObjectivesApi.ListObjectives(ctx, expr).Execute()
319325
if err != nil {
320326
var apiErr openapiclient.GenericOpenAPIError
321327
if errors.As(err, &apiErr) {
@@ -326,7 +332,11 @@ func (o *ObjectivesServer) GetObjectiveStatus(ctx context.Context, expr string)
326332

327333
return openapiserver.ImplResponse{Code: http.StatusInternalServerError}, err
328334
}
329-
objective := openapi.InternalFromClient(clientObjective)
335+
if len(clientObjectives) != 1 {
336+
return openapiserver.ImplResponse{Code: http.StatusBadRequest}, fmt.Errorf("expr matches not exactly one SLO, it matches: %d", len(clientObjectives))
337+
}
338+
339+
objective := openapi.InternalFromClient(clientObjectives[0])
330340

331341
ts := RoundUp(time.Now().UTC(), 5*time.Minute)
332342

0 commit comments

Comments
 (0)