From 3da4dce29e05881a34135da338176d8d891f5e68 Mon Sep 17 00:00:00 2001 From: Eric L <100242256+splunkericl@users.noreply.github.com> Date: Thu, 14 Dec 2023 16:18:32 -0800 Subject: [PATCH] return JSON ok response for hec receiver raw path (#29875) **Description:** Adds enhancement for splunk hec receiver raw path - previously it returns no response in http body: it nows returns json response **Link to tracking Issue:** https://github.com/open-telemetry/opentelemetry-collector-contrib/issues/29745 --- ...splunkhecreceiver-return-success-body.yaml | 27 ++++++ receiver/splunkhecreceiver/receiver.go | 17 ++-- receiver/splunkhecreceiver/receiver_test.go | 89 ++++++++++++------- 3 files changed, 94 insertions(+), 39 deletions(-) create mode 100644 .chloggen/splunkhecreceiver-return-success-body.yaml diff --git a/.chloggen/splunkhecreceiver-return-success-body.yaml b/.chloggen/splunkhecreceiver-return-success-body.yaml new file mode 100644 index 000000000000..4027c62f7feb --- /dev/null +++ b/.chloggen/splunkhecreceiver-return-success-body.yaml @@ -0,0 +1,27 @@ +# Use this changelog template to create an entry for release notes. + +# One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix' +change_type: enhancement + +# The name of the component, or a single word describing the area of concern, (e.g. filelogreceiver) +component: splunkhecreceiver + +# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`). +note: Returns json response in raw endpoint when it is successful + +# Mandatory: One or more tracking issues related to the change. You can use the PR number here if no issue exists. +issues: [20766] + +# (Optional) One or more lines of additional information to render under the primary note. +# These lines will be padded with 2 spaces and then inserted directly into the document. +# Use pipe (|) for multiline entries. +subtext: + +# If your change doesn't affect end users or the exported elements of any package, +# you should instead start your pull request title with [chore] or use the "Skip Changelog" label. +# Optional: The change log or logs in which this entry should be included. +# e.g. '[user]' or '[user, api]' +# Include 'user' if the change is relevant to end users. +# Include 'api' if there is a change to a library API. +# Default: '[user]' +change_logs: [user] diff --git a/receiver/splunkhecreceiver/receiver.go b/receiver/splunkhecreceiver/receiver.go index 6f62bc6dcd8e..ccf13b18be93 100644 --- a/receiver/splunkhecreceiver/receiver.go +++ b/receiver/splunkhecreceiver/receiver.go @@ -49,6 +49,8 @@ const ( // Centralizing some HTTP and related string constants. gzipEncoding = "gzip" httpContentEncodingHeader = "Content-Encoding" + httpContentTypeHeader = "Content-Type" + httpJSONTypeHeader = "application/json" ) var ( @@ -229,6 +231,14 @@ func (r *splunkReceiver) Shutdown(context.Context) error { return err } +func (r *splunkReceiver) writeSuccessResponse(ctx context.Context, resp http.ResponseWriter, eventCount int) { + resp.Header().Set(httpContentTypeHeader, httpJSONTypeHeader) + resp.WriteHeader(http.StatusOK) + if _, err := resp.Write(okRespBody); err != nil { + r.failRequest(ctx, resp, http.StatusInternalServerError, errInternalServerError, eventCount, err) + } +} + func (r *splunkReceiver) handleRawReq(resp http.ResponseWriter, req *http.Request) { ctx := req.Context() ctx = r.obsrecv.StartLogsOp(ctx) @@ -292,7 +302,7 @@ func (r *splunkReceiver) handleRawReq(resp http.ResponseWriter, req *http.Reques if consumerErr != nil { r.failRequest(ctx, resp, http.StatusInternalServerError, errInternalServerError, slLen, consumerErr) } else { - resp.WriteHeader(http.StatusOK) + r.writeSuccessResponse(ctx, resp, ld.LogRecordCount()) r.obsrecv.EndLogsOp(ctx, metadata.Type, slLen, nil) } } @@ -404,10 +414,7 @@ func (r *splunkReceiver) handleReq(resp http.ResponseWriter, req *http.Request) } } - resp.WriteHeader(http.StatusOK) - if _, err := resp.Write(okRespBody); err != nil { - r.failRequest(ctx, resp, http.StatusInternalServerError, errInternalServerError, len(events)+len(metricEvents), err) - } + r.writeSuccessResponse(ctx, resp, len(events)+len(metricEvents)) } func (r *splunkReceiver) createResourceCustomizer(req *http.Request) func(resource pcommon.Resource) { diff --git a/receiver/splunkhecreceiver/receiver_test.go b/receiver/splunkhecreceiver/receiver_test.go index 6ae210f768b3..4fc29b3d07da 100644 --- a/receiver/splunkhecreceiver/receiver_test.go +++ b/receiver/splunkhecreceiver/receiver_test.go @@ -37,6 +37,13 @@ import ( "github.com/open-telemetry/opentelemetry-collector-contrib/internal/splunk" ) +func assertHecSuccessResponse(t *testing.T, resp *http.Response, body any) { + status := resp.StatusCode + assert.Equal(t, http.StatusOK, status) + assert.Equal(t, httpJSONTypeHeader, resp.Header.Get(httpContentTypeHeader)) + assert.Equal(t, map[string]any{"code": float64(0), "text": "Success"}, body) +} + func Test_splunkhecreceiver_NewLogsReceiver(t *testing.T) { defaultConfig := createDefaultConfig().(*Config) emptyEndpointConfig := createDefaultConfig().(*Config) @@ -167,14 +174,15 @@ func Test_splunkhecReceiver_handleReq(t *testing.T) { tests := []struct { name string req *http.Request - assertResponse func(t *testing.T, status int, body any) + assertResponse func(t *testing.T, resp *http.Response, body any) assertSink func(t *testing.T, sink *consumertest.LogsSink) assertMetricsSink func(t *testing.T, sink *consumertest.MetricsSink) }{ { name: "incorrect_method", req: httptest.NewRequest("PUT", "http://localhost/foo", nil), - assertResponse: func(t *testing.T, status int, body any) { + assertResponse: func(t *testing.T, resp *http.Response, body any) { + status := resp.StatusCode assert.Equal(t, http.StatusBadRequest, status) assert.Equal(t, "Only \"POST\" method is supported", body) }, @@ -188,7 +196,8 @@ func Test_splunkhecReceiver_handleReq(t *testing.T) { req.Header.Set("Content-Type", "application/not-json") return req }(), - assertResponse: func(t *testing.T, status int, body any) { + assertResponse: func(t *testing.T, resp *http.Response, body any) { + status := resp.StatusCode assert.Equal(t, http.StatusOK, status) assert.Equal(t, map[string]any{ "text": "Success", @@ -203,7 +212,8 @@ func Test_splunkhecReceiver_handleReq(t *testing.T) { req.Header.Set("Content-Encoding", "superzipper") return req }(), - assertResponse: func(t *testing.T, status int, body any) { + assertResponse: func(t *testing.T, resp *http.Response, body any) { + status := resp.StatusCode assert.Equal(t, http.StatusUnsupportedMediaType, status) assert.Equal(t, `"Content-Encoding" must be "gzip" or empty`, body) }, @@ -214,7 +224,8 @@ func Test_splunkhecReceiver_handleReq(t *testing.T) { req := httptest.NewRequest("POST", "http://localhost/foo", bytes.NewReader([]byte{1, 2, 3, 4})) return req }(), - assertResponse: func(t *testing.T, status int, body any) { + assertResponse: func(t *testing.T, resp *http.Response, body any) { + status := resp.StatusCode assert.Equal(t, http.StatusBadRequest, status) assert.Equal(t, map[string]any{"code": float64(6), "text": "Invalid data format"}, body) }, @@ -225,7 +236,8 @@ func Test_splunkhecReceiver_handleReq(t *testing.T) { req := httptest.NewRequest("POST", "http://localhost/foo", bytes.NewReader(nil)) return req }(), - assertResponse: func(t *testing.T, status int, body any) { + assertResponse: func(t *testing.T, resp *http.Response, body any) { + status := resp.StatusCode assert.Equal(t, http.StatusBadRequest, status) assert.Equal(t, map[string]any{"code": float64(5), "text": "No data"}, body) }, @@ -238,7 +250,8 @@ func Test_splunkhecReceiver_handleReq(t *testing.T) { req := httptest.NewRequest("POST", "http://localhost/foo", bytes.NewReader(msgBytes)) return req }(), - assertResponse: func(t *testing.T, status int, body any) { + assertResponse: func(t *testing.T, resp *http.Response, body any) { + status := resp.StatusCode assert.Equal(t, http.StatusBadRequest, status) assert.Equal(t, map[string]any{"code": float64(6), "text": "Invalid data format"}, body) }, @@ -253,7 +266,8 @@ func Test_splunkhecReceiver_handleReq(t *testing.T) { req := httptest.NewRequest("POST", "http://localhost/foo", bytes.NewReader(msgBytes)) return req }(), - assertResponse: func(t *testing.T, status int, body any) { + assertResponse: func(t *testing.T, resp *http.Response, body any) { + status := resp.StatusCode assert.Equal(t, http.StatusBadRequest, status) assert.Equal(t, map[string]any{"code": float64(12), "text": "Event field is required"}, body) }, @@ -268,7 +282,8 @@ func Test_splunkhecReceiver_handleReq(t *testing.T) { req := httptest.NewRequest("POST", "http://localhost/foo", bytes.NewReader(msgBytes)) return req }(), - assertResponse: func(t *testing.T, status int, body any) { + assertResponse: func(t *testing.T, resp *http.Response, body any) { + status := resp.StatusCode assert.Equal(t, http.StatusBadRequest, status) assert.Equal(t, map[string]any{"code": float64(13), "text": "Event field cannot be blank"}, body) }, @@ -281,9 +296,8 @@ func Test_splunkhecReceiver_handleReq(t *testing.T) { req := httptest.NewRequest("POST", "http://localhost/foo", bytes.NewReader(msgBytes)) return req }(), - assertResponse: func(t *testing.T, status int, body any) { - assert.Equal(t, http.StatusOK, status) - assert.Equal(t, map[string]any{"code": float64(0), "text": "Success"}, body) + assertResponse: func(t *testing.T, resp *http.Response, body any) { + assertHecSuccessResponse(t, resp, body) }, assertSink: func(t *testing.T, sink *consumertest.LogsSink) { assert.Equal(t, 1, len(sink.AllLogs())) @@ -300,9 +314,8 @@ func Test_splunkhecReceiver_handleReq(t *testing.T) { req := httptest.NewRequest("POST", "http://localhost/foo", bytes.NewReader(msgBytes)) return req }(), - assertResponse: func(t *testing.T, status int, body any) { - assert.Equal(t, http.StatusOK, status) - assert.Equal(t, map[string]any{"code": float64(0), "text": "Success"}, body) + assertResponse: func(t *testing.T, resp *http.Response, body any) { + assertHecSuccessResponse(t, resp, body) }, assertSink: func(t *testing.T, sink *consumertest.LogsSink) { assert.Equal(t, 0, len(sink.AllLogs())) @@ -327,9 +340,8 @@ func Test_splunkhecReceiver_handleReq(t *testing.T) { req.Header.Set("Content-Encoding", "gzip") return req }(), - assertResponse: func(t *testing.T, status int, body any) { - assert.Equal(t, http.StatusOK, status) - assert.Equal(t, map[string]any{"code": float64(0), "text": "Success"}, body) + assertResponse: func(t *testing.T, resp *http.Response, body any) { + assertHecSuccessResponse(t, resp, body) }, }, { @@ -342,7 +354,8 @@ func Test_splunkhecReceiver_handleReq(t *testing.T) { req.Header.Set("Content-Encoding", "gzip") return req }(), - assertResponse: func(t *testing.T, status int, body any) { + assertResponse: func(t *testing.T, resp *http.Response, body any) { + status := resp.StatusCode assert.Equal(t, http.StatusBadRequest, status) assert.Equal(t, `Error on gzip body`, body) }, @@ -373,7 +386,7 @@ func Test_splunkhecReceiver_handleReq(t *testing.T) { fmt.Println(string(respBytes)) assert.NoError(t, json.Unmarshal(respBytes, &body)) - tt.assertResponse(t, resp.StatusCode, body) + tt.assertResponse(t, resp, body) if tt.assertSink != nil { tt.assertSink(t, sink) } @@ -928,12 +941,13 @@ func Test_splunkhecReceiver_handleRawReq(t *testing.T) { tests := []struct { name string req *http.Request - assertResponse func(t *testing.T, status int, body any) + assertResponse func(t *testing.T, resp *http.Response, body any) }{ { name: "incorrect_method", req: httptest.NewRequest("PUT", "http://localhost/foo", nil), - assertResponse: func(t *testing.T, status int, body any) { + assertResponse: func(t *testing.T, resp *http.Response, body any) { + status := resp.StatusCode assert.Equal(t, http.StatusBadRequest, status) assert.Equal(t, `Only "POST" method is supported`, body) }, @@ -945,7 +959,8 @@ func Test_splunkhecReceiver_handleRawReq(t *testing.T) { req.Header.Set("Content-Type", "application/not-json") return req }(), - assertResponse: func(t *testing.T, status int, body any) { + assertResponse: func(t *testing.T, resp *http.Response, body any) { + status := resp.StatusCode assert.Equal(t, http.StatusOK, status) }, }, @@ -956,7 +971,8 @@ func Test_splunkhecReceiver_handleRawReq(t *testing.T) { req.Header.Set("Content-Encoding", "superzipper") return req }(), - assertResponse: func(t *testing.T, status int, body any) { + assertResponse: func(t *testing.T, resp *http.Response, body any) { + status := resp.StatusCode assert.Equal(t, http.StatusUnsupportedMediaType, status) assert.Equal(t, `"Content-Encoding" must be "gzip" or empty`, body) }, @@ -967,7 +983,8 @@ func Test_splunkhecReceiver_handleRawReq(t *testing.T) { req := httptest.NewRequest("POST", "http://localhost/foo", bytes.NewReader(nil)) return req }(), - assertResponse: func(t *testing.T, status int, body any) { + assertResponse: func(t *testing.T, resp *http.Response, body any) { + status := resp.StatusCode assert.Equal(t, http.StatusBadRequest, status) assert.Equal(t, map[string]any{"code": float64(5), "text": "No data"}, body) }, @@ -979,7 +996,8 @@ func Test_splunkhecReceiver_handleRawReq(t *testing.T) { req := httptest.NewRequest("POST", "http://localhost/foo", strings.NewReader("foo\nbar")) return req }(), - assertResponse: func(t *testing.T, status int, body any) { + assertResponse: func(t *testing.T, resp *http.Response, body any) { + status := resp.StatusCode assert.Equal(t, http.StatusOK, status) }, }, @@ -991,8 +1009,8 @@ func Test_splunkhecReceiver_handleRawReq(t *testing.T) { req := httptest.NewRequest("POST", "http://localhost/foo", bytes.NewReader(msgBytes)) return req }(), - assertResponse: func(t *testing.T, status int, body any) { - assert.Equal(t, http.StatusOK, status) + assertResponse: func(t *testing.T, resp *http.Response, body any) { + assertHecSuccessResponse(t, resp, body) }, }, { @@ -1011,8 +1029,8 @@ func Test_splunkhecReceiver_handleRawReq(t *testing.T) { req.Header.Set("Content-Encoding", "gzip") return req }(), - assertResponse: func(t *testing.T, status int, body any) { - assert.Equal(t, http.StatusOK, status) + assertResponse: func(t *testing.T, resp *http.Response, body any) { + assertHecSuccessResponse(t, resp, body) }, }, { @@ -1025,7 +1043,8 @@ func Test_splunkhecReceiver_handleRawReq(t *testing.T) { req.Header.Set("Content-Encoding", "gzip") return req }(), - assertResponse: func(t *testing.T, status int, body any) { + assertResponse: func(t *testing.T, resp *http.Response, body any) { + status := resp.StatusCode assert.Equal(t, http.StatusBadRequest, status) assert.Equal(t, `Error on gzip body`, body) }, @@ -1044,7 +1063,8 @@ func Test_splunkhecReceiver_handleRawReq(t *testing.T) { return req }(), - assertResponse: func(t *testing.T, status int, body any) { + assertResponse: func(t *testing.T, resp *http.Response, body any) { + status := resp.StatusCode assert.Equal(t, http.StatusBadRequest, status) assert.Equal(t, map[string]any{"code": float64(6), "text": "Invalid data format"}, body) }, @@ -1063,7 +1083,8 @@ func Test_splunkhecReceiver_handleRawReq(t *testing.T) { return req }(), - assertResponse: func(t *testing.T, status int, body any) { + assertResponse: func(t *testing.T, resp *http.Response, body any) { + status := resp.StatusCode assert.Equal(t, http.StatusBadRequest, status) assert.Equal(t, map[string]any{"code": float64(6), "text": "Invalid data format"}, body) }, @@ -1094,7 +1115,7 @@ func Test_splunkhecReceiver_handleRawReq(t *testing.T) { assert.NoError(t, json.Unmarshal(respBytes, &body)) } - tt.assertResponse(t, resp.StatusCode, body) + tt.assertResponse(t, resp, body) }) } }