Skip to content

Commit

Permalink
Change UTF-8 flag name and validations
Browse files Browse the repository at this point in the history
Signed-off-by: Federico Torres <[email protected]>
  • Loading branch information
fedetorres93 committed Sep 23, 2024
1 parent 83c45eb commit 90f4e80
Show file tree
Hide file tree
Showing 3 changed files with 23 additions and 44 deletions.
50 changes: 12 additions & 38 deletions handler/handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,14 @@ import (
"context"
"errors"
"fmt"
"github.com/prometheus/common/model"
"net/http"
"net/http/httptest"
"testing"
"time"

"github.com/go-kit/log"
"github.com/matttproud/golang_protobuf_extensions/pbutil"
"github.com/prometheus/common/model"
"github.com/prometheus/common/route"
"google.golang.org/protobuf/encoding/prototext"
"google.golang.org/protobuf/proto"
Expand Down Expand Up @@ -439,6 +439,7 @@ func TestPush(t *testing.T) {

func TestPushUTF8(t *testing.T) {
model.NameValidationScheme = model.UTF8Validation
EscapingScheme = model.ValueEncodingEscaping
mms := MockMetricStore{}
handler := Push(&mms, false, true, false, logger)
handlerBase64 := Push(&mms, false, true, true, logger)
Expand Down Expand Up @@ -622,27 +623,7 @@ func TestPushUTF8(t *testing.T) {
verifyMetricFamily(t, `name:"histogram.metric" type:HISTOGRAM metric:{histogram:{sample_count_float:20 sample_sum:99.23 schema:1 negative_span:{offset:0 length:2} negative_span:{offset:0 length:2} negative_count:2 negative_count:2 negative_count:-2 negative_count:0 positive_span:{offset:0 length:2} positive_span:{offset:0 length:2} positive_count:2 positive_count:2 positive_count:-2 positive_count:0}}`, mms.lastWriteRequest.MetricFamilies["histogram.metric"])

model.NameValidationScheme = model.LegacyValidation

// With job name, instance name, UTF-8 escaped label name in params, UTF-8 metric name, text content and legacy validation.
mms.lastWriteRequest = storage.WriteRequest{}
req, err = http.NewRequest(
"POST", "http://example.org/",
bytes.NewBufferString("some_metric 3.14\n{\"another.metric\",instance=\"testinstance\",job=\"testjob\",\"dotted.label.name\"=\"mylabelvalue\"} 42\n"),
)
if err != nil {
t.Fatal(err)
}
w = httptest.NewRecorder()

params = map[string]string{
"job": "testjob",
"labels": "/instance/testinstance/U__dotted_2e_label_2e_name/mylabelvalue",
}

handler(w, req.WithContext(ctxWithParams(params, req)))
if expected, got := http.StatusBadRequest, w.Code; expected != got {
t.Errorf("Wanted status code %v, got %v.", expected, got)
}
EscapingScheme = model.NoEscaping
}

func TestDelete(t *testing.T) {
Expand Down Expand Up @@ -736,6 +717,7 @@ func TestDelete(t *testing.T) {

func TestDeleteUTF8(t *testing.T) {
model.NameValidationScheme = model.UTF8Validation
EscapingScheme = model.ValueEncodingEscaping
mms := MockMetricStore{}
handler := Delete(&mms, false, logger)
handlerBase64 := Delete(&mms, true, logger)
Expand Down Expand Up @@ -795,20 +777,7 @@ func TestDeleteUTF8(t *testing.T) {
}

model.NameValidationScheme = model.LegacyValidation

// With job name, instance name, UTF-8 escaped label name and legacy validation.
mms.lastWriteRequest = storage.WriteRequest{}
w = httptest.NewRecorder()

params = map[string]string{
"job": "testjob",
"labels": "/instance/testinstance/U__dotted_2e_label_2e_name/mylabelvalue",
}

handler(w, req.WithContext(ctxWithParams(params, req)))
if expected, got := http.StatusBadRequest, w.Code; expected != got {
t.Errorf("Wanted status code %v, got %v.", expected, got)
}
EscapingScheme = model.NoEscaping
}

func TestSplitLabels(t *testing.T) {
Expand Down Expand Up @@ -862,8 +831,11 @@ func TestSplitLabels(t *testing.T) {
expectError: true,
},
"regular label and UTF-8 escaped label name with legacy validation": {
input: "/label_name1/label_value1/U__label_2e_name2/label_value2",
expectError: true,
input: "/label_name1/label_value1/U__label_2e_name2/label_value2",
expectedOutput: map[string]string{
"label_name1": "label_value1",
"U__label_2e_name2": "label_value2",
},
},
}

Expand Down Expand Up @@ -916,6 +888,7 @@ func TestSplitLabelsUTF8(t *testing.T) {
}

model.NameValidationScheme = model.UTF8Validation
EscapingScheme = model.ValueEncodingEscaping

for name, scenario := range scenarios {
t.Run(name, func(t *testing.T) {
Expand Down Expand Up @@ -943,6 +916,7 @@ func TestSplitLabelsUTF8(t *testing.T) {
}

model.NameValidationScheme = model.LegacyValidation
EscapingScheme = model.NoEscaping
}

func TestWipeMetricStore(t *testing.T) {
Expand Down
7 changes: 4 additions & 3 deletions handler/push.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ const (
Base64Suffix = "@base64"
)

var EscapingScheme = model.NoEscaping

// Push returns an http.Handler which accepts samples over HTTP and stores them
// in the MetricStore. If replace is true, all metrics for the job and instance
// given by the request are deleted before new ones are stored. If check is
Expand Down Expand Up @@ -180,9 +182,8 @@ func splitLabels(labels string) (map[string]string, error) {
for i := 0; i < len(components)-1; i += 2 {
name, value := components[i], components[i+1]
trimmedName := strings.TrimSuffix(name, Base64Suffix)
unescapedName := model.UnescapeName(trimmedName, model.ValueEncodingEscaping)
if !model.LabelNameRE.MatchString(trimmedName) ||
!model.LabelName(unescapedName).IsValid() ||
unescapedName := model.UnescapeName(trimmedName, EscapingScheme)
if !model.LabelName(unescapedName).IsValid() ||
strings.HasPrefix(trimmedName, model.ReservedLabelPrefix) {
return nil, fmt.Errorf("improper label name %q", trimmedName)
}
Expand Down
10 changes: 7 additions & 3 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ import (
"compress/gzip"
"context"
"fmt"
"github.com/prometheus/common/model"
"io"
"net/http"
"net/http/pprof"
Expand All @@ -36,6 +35,7 @@ import (
"github.com/prometheus/client_golang/prometheus"
versioncollector "github.com/prometheus/client_golang/prometheus/collectors/version"
"github.com/prometheus/client_golang/prometheus/promhttp"
"github.com/prometheus/common/model"
"github.com/prometheus/common/promlog"
"github.com/prometheus/common/route"
"github.com/prometheus/common/version"
Expand Down Expand Up @@ -74,7 +74,7 @@ func main() {
persistenceFile = app.Flag("persistence.file", "File to persist metrics. If empty, metrics are only kept in memory.").Default("").String()
persistenceInterval = app.Flag("persistence.interval", "The minimum interval at which to write out the persistence file.").Default("5m").Duration()
pushUnchecked = app.Flag("push.disable-consistency-check", "Do not check consistency of pushed metrics. DANGEROUS.").Default("false").Bool()
pushEscaped = app.Flag("push.enable-escaped-labels", "Allow escaped characters in label names in URLs.").Default("false").Bool()
pushUTF8Names = app.Flag("push.enable-utf8-names", "Allow UTF-8 characters in metric and label names.").Default("false").Bool()
promlogConfig = promlog.Config{}
)
promlogflag.AddFlags(app, &promlogConfig)
Expand Down Expand Up @@ -104,8 +104,12 @@ func main() {

ms := storage.NewDiskMetricStore(*persistenceFile, *persistenceInterval, prometheus.DefaultGatherer, logger)

if *pushEscaped {
if *pushUTF8Names {
model.NameValidationScheme = model.UTF8Validation
handler.EscapingScheme = model.ValueEncodingEscaping
} else {
model.NameValidationScheme = model.LegacyValidation
handler.EscapingScheme = model.NoEscaping
}

// Create a Gatherer combining the DefaultGatherer and the metrics from the metric store.
Expand Down

0 comments on commit 90f4e80

Please sign in to comment.