Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix regression when parsing numbers in Push request #10550

Merged
merged 1 commit into from
Sep 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 2 additions & 9 deletions pkg/loghttp/query.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,15 +69,8 @@ func (s *LogProtoStream) UnmarshalJSON(data []byte) error {
err := jsonparser.ObjectEach(data, func(key, val []byte, ty jsonparser.ValueType, _ int) error {
switch string(key) {
case "stream":
labels := make(LabelSet)
err := jsonparser.ObjectEach(val, func(key, val []byte, dataType jsonparser.ValueType, _ int) error {
if dataType != jsonparser.String {
return jsonparser.MalformedStringError
}
labels[string(key)] = string(val)
return nil
})
if err != nil {
var labels LabelSet
if err := labels.UnmarshalJSON(val); err != nil {
return err
}
s.Labels = labels.String()
Expand Down
164 changes: 126 additions & 38 deletions pkg/util/unmarshal/unmarshal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"testing"
"time"

"github.com/prometheus/prometheus/model/labels"
"github.com/stretchr/testify/require"

"github.com/grafana/loki/pkg/loghttp"
Expand All @@ -15,24 +16,28 @@ import (
"github.com/grafana/loki/pkg/util/marshal"
)

// covers requests to /loki/api/v1/push
var pushTests = []struct {
expected []logproto.Stream
actual string
}{
{
[]logproto.Stream{
{
Entries: []logproto.Entry{
{
Timestamp: time.Unix(0, 123456789012345),
Line: "super line",
func Test_DecodePushRequest(t *testing.T) {
// covers requests to /loki/api/v1/push
for _, tc := range []struct {
name string
expected []logproto.Stream
expectedErr bool
actual string
}{
{
name: "basic",
expected: []logproto.Stream{
{
Entries: []logproto.Entry{
{
Timestamp: time.Unix(0, 123456789012345),
Line: "super line",
},
},
Labels: labels.FromStrings("test", "test").String(),
},
Labels: `{test="test"}`,
},
},
`{
actual: `{
"streams": [
{
"stream": {
Expand All @@ -44,24 +49,25 @@ var pushTests = []struct {
}
]
}`,
},
{
[]logproto.Stream{
{
Entries: []logproto.Entry{
{
Timestamp: time.Unix(0, 123456789012345),
Line: "super line",
StructuredMetadata: []logproto.LabelAdapter{
{Name: "a", Value: "1"},
{Name: "b", Value: "2"},
},
{
name: "with structured metadata",
expected: []logproto.Stream{
{
Entries: []logproto.Entry{
{
Timestamp: time.Unix(0, 123456789012345),
Line: "super line",
StructuredMetadata: []logproto.LabelAdapter{
{Name: "a", Value: "1"},
{Name: "b", Value: "2"},
},
},
},
Labels: labels.FromStrings("test", "test").String(),
},
Labels: `{test="test"}`,
},
},
`{
actual: `{
"streams": [
{
"stream": {
Expand All @@ -73,18 +79,100 @@ var pushTests = []struct {
}
]
}`,
},
}
},

func Test_DecodePushRequest(t *testing.T) {
for i, pushTest := range pushTests {
var actual logproto.PushRequest
closer := io.NopCloser(strings.NewReader(pushTest.actual))
// The following test cases are added to cover a regression. Even though the Loki HTTP API
// docs for the push endpoint state that the stream label values should be strings, we
// previously didn't enforce this requirement.
// With https://github.com/grafana/loki/pull/9694, we started enforcing this requirement
// and that broke some users. We are adding these test cases to ensure that we don't
// enforce this requirement in the future. Note that we may want to enforce this requirement
// in a future major release, in which case we should modify these test cases.
{
name: "number in stream label value",
expected: []logproto.Stream{
{
Entries: []logproto.Entry{
{
Timestamp: time.Unix(0, 123456789012345),
Line: "super line",
},
},
Labels: labels.FromStrings("test", "test", "number", "123").String(),
},
},
actual: `{
"streams": [
{
"stream": {
"test": "test",
"number": 123
},
"values":[
[ "123456789012345", "super line" ]
]
}
]
}`,
},
{
name: "string without quotes in stream label value",
expectedErr: true,
actual: `{
"streams": [
{
"stream": {
"test": "test",
"text": None
},
"values":[
[ "123456789012345", "super line" ]
]
}
]
}`,
},
{
name: "json object in stream label value",
expected: []logproto.Stream{
{
Entries: []logproto.Entry{
{
Timestamp: time.Unix(0, 123456789012345),
Line: "super line",
},
},
Labels: labels.FromStrings("test", "test", "text", "{ \"a\": \"b\" }").String(),
},
},
actual: `{
"streams": [
{
"stream": {
"test": "test",
"text": { "a": "b" }
},
"values":[
[ "123456789012345", "super line" ]
]
}
]
}`,
},
} {
t.Run(tc.name, func(t *testing.T) {
var actual logproto.PushRequest
closer := io.NopCloser(strings.NewReader(tc.actual))

err := DecodePushRequest(closer, &actual)
require.NoError(t, err)
err := DecodePushRequest(closer, &actual)
if tc.expectedErr {
require.Error(t, err)
return
}
require.NoError(t, err)

require.Equalf(t, pushTest.expected, actual.Streams, "Push Test %d failed", i)
require.Equal(t, tc.expected, actual.Streams)
})
}
}

Expand Down