From c0b93d3b43bbf8594fcb1aade0c042d7c5181722 Mon Sep 17 00:00:00 2001 From: omrozowicz-splunk Date: Wed, 12 Apr 2023 11:08:14 +0200 Subject: [PATCH 1/5] make splunk_hec receiver healthcheck mimic the real Splunk HEC behaviour --- receiver/splunkhecreceiver/receiver.go | 11 ++- receiver/splunkhecreceiver/receiver_test.go | 82 +++++++++++++++++++++ 2 files changed, 91 insertions(+), 2 deletions(-) diff --git a/receiver/splunkhecreceiver/receiver.go b/receiver/splunkhecreceiver/receiver.go index b9752346da3a..68791e042ec5 100644 --- a/receiver/splunkhecreceiver/receiver.go +++ b/receiver/splunkhecreceiver/receiver.go @@ -43,6 +43,7 @@ const ( defaultServerTimeout = 20 * time.Second responseOK = "OK" + responseHecHealthy = `{"text": "HEC is healthy", "code": 17}` responseInvalidMethod = `Only "POST" method is supported` responseInvalidEncoding = `"Content-Encoding" must be "gzip" or empty` responseInvalidDataFormat = `{"text":"Invalid data format","code":6}` @@ -202,7 +203,8 @@ func (r *splunkReceiver) Start(_ context.Context, host component.Host) error { } mx := mux.NewRouter() - mx.NewRoute().Path(r.config.HealthPath).HandlerFunc(r.handleHealthReq) + mx.NewRoute().Path(r.config.HealthPath).HandlerFunc(r.handleHealthReq).Methods("GET") + mx.NewRoute().Path(r.config.HealthPath + "/1.0").HandlerFunc(r.handleHealthReq).Methods("GET") if r.logsConsumer != nil { mx.NewRoute().Path(r.config.RawPath).HandlerFunc(r.handleRawReq) } @@ -458,7 +460,12 @@ func (r *splunkReceiver) failRequest( } func (r *splunkReceiver) handleHealthReq(writer http.ResponseWriter, _ *http.Request) { - writer.WriteHeader(200) + writer.Header().Add("Content-Type", "application/json") + writer.WriteHeader(http.StatusOK) + _, err := writer.Write([]byte(responseHecHealthy)) + if err != nil { + return + } } func initJSONResponse(s string) []byte { diff --git a/receiver/splunkhecreceiver/receiver_test.go b/receiver/splunkhecreceiver/receiver_test.go index 1c1db2ec2eea..5048aba3bfcb 100644 --- a/receiver/splunkhecreceiver/receiver_test.go +++ b/receiver/splunkhecreceiver/receiver_test.go @@ -1268,3 +1268,85 @@ func BenchmarkHandleReq(b *testing.B) { assert.NoError(b, err) } } + +func Test_splunkhecReceiver_healthCheck_success(t *testing.T) { + config := createDefaultConfig().(*Config) + config.Endpoint = "localhost:0" // Actually not creating the endpoint + + tests := []struct { + name string + req *http.Request + assertResponse func(t *testing.T, status int, body string) + }{ + { + name: "correct_healthcheck", + req: func() *http.Request { + req := httptest.NewRequest("GET", "http://localhost:0/services/collector/health", nil) + return req + }(), + assertResponse: func(t *testing.T, status int, body string) { + assert.Equal(t, http.StatusOK, status) + assert.Equal(t, responseHecHealthy, body) + }, + }, + { + name: "correct_healthcheck_v1", + req: func() *http.Request { + req := httptest.NewRequest("GET", "http://localhost:0/services/collector/health/1.0", nil) + return req + }(), + assertResponse: func(t *testing.T, status int, body string) { + assert.Equal(t, http.StatusOK, status) + assert.Equal(t, responseHecHealthy, body) + }, + }, + { + name: "incorrect_healthcheck_methods", + req: func() *http.Request { + req := httptest.NewRequest("POST", "http://localhost:0/services/collector/health", nil) + return req + }(), + assertResponse: func(t *testing.T, status int, body string) { + assert.Equal(t, http.StatusBadRequest, status) + assert.Equal(t, responseNoData, body) + }, + }, + { + name: "incorrect_healthcheck_methods_v1", + req: func() *http.Request { + req := httptest.NewRequest("POST", "http://localhost:0/services/collector/health/1.0", nil) + return req + }(), + assertResponse: func(t *testing.T, status int, body string) { + assert.Equal(t, http.StatusBadRequest, status) + assert.Equal(t, responseNoData, body) + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + sink := new(consumertest.LogsSink) + rcv, err := newLogsReceiver(receivertest.NewNopCreateSettings(), *config, sink) + assert.NoError(t, err) + + r := rcv.(*splunkReceiver) + assert.NoError(t, r.Start(context.Background(), componenttest.NewNopHost())) + defer func() { + assert.NoError(t, r.Shutdown(context.Background())) + }() + + w := httptest.NewRecorder() + r.server.Handler.ServeHTTP(w, tt.req) + resp := w.Result() + respBytes, err := io.ReadAll(resp.Body) + assert.NoError(t, err) + var bodyStr string + if err := json.Unmarshal(respBytes, &bodyStr); err != nil { + bodyStr = string(respBytes) + } + + tt.assertResponse(t, resp.StatusCode, bodyStr) + }) + } +} From c3d0ff77c8351d855c17137b1bd8113ce61c3c32 Mon Sep 17 00:00:00 2001 From: omrozowicz-splunk Date: Wed, 12 Apr 2023 11:33:16 +0200 Subject: [PATCH 2/5] add to changelog --- .chloggen/mimic-hec-healthcheck-endpoint.yaml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 .chloggen/mimic-hec-healthcheck-endpoint.yaml diff --git a/.chloggen/mimic-hec-healthcheck-endpoint.yaml b/.chloggen/mimic-hec-healthcheck-endpoint.yaml new file mode 100644 index 000000000000..3437e0fee2c2 --- /dev/null +++ b/.chloggen/mimic-hec-healthcheck-endpoint.yaml @@ -0,0 +1,16 @@ +# 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: Make Splunk HEC receiver Health endpoint mimic the real one + +# One or more tracking issues related to the change +issues: [20871] + +# (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: From c494bffa484023cb9523becf683eb49bc64ee7cd Mon Sep 17 00:00:00 2001 From: omrozowicz-splunk Date: Wed, 12 Apr 2023 11:59:12 +0200 Subject: [PATCH 3/5] update HealthPath unit tests --- receiver/splunkhecreceiver/receiver_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/receiver/splunkhecreceiver/receiver_test.go b/receiver/splunkhecreceiver/receiver_test.go index 5048aba3bfcb..48c58e0948f4 100644 --- a/receiver/splunkhecreceiver/receiver_test.go +++ b/receiver/splunkhecreceiver/receiver_test.go @@ -1042,12 +1042,12 @@ func Test_splunkhecreceiver_handleHealthPath(t *testing.T) { assert.NoError(t, r.Shutdown(context.Background())) }() w := httptest.NewRecorder() - r.handleHealthReq(w, httptest.NewRequest("POST", "http://localhost/services/collector/health", nil)) + r.handleHealthReq(w, httptest.NewRequest("GET", "http://localhost/services/collector/health", nil)) resp := w.Result() respBytes, err := io.ReadAll(resp.Body) assert.NoError(t, err) - assert.Len(t, respBytes, 0) + assert.Equal(t, string(respBytes), responseHecHealthy) assert.Equal(t, 200, resp.StatusCode) } From e79081d6b0cc3099058bde5998ebe39e78e16541 Mon Sep 17 00:00:00 2001 From: omrozowicz-splunk Date: Thu, 13 Apr 2023 08:42:59 +0200 Subject: [PATCH 4/5] bring back the change with accepting only GETs --- receiver/splunkhecreceiver/receiver.go | 7 ++----- receiver/splunkhecreceiver/receiver_test.go | 11 ----------- 2 files changed, 2 insertions(+), 16 deletions(-) diff --git a/receiver/splunkhecreceiver/receiver.go b/receiver/splunkhecreceiver/receiver.go index 68791e042ec5..f5ef7c9db9cf 100644 --- a/receiver/splunkhecreceiver/receiver.go +++ b/receiver/splunkhecreceiver/receiver.go @@ -203,7 +203,7 @@ func (r *splunkReceiver) Start(_ context.Context, host component.Host) error { } mx := mux.NewRouter() - mx.NewRoute().Path(r.config.HealthPath).HandlerFunc(r.handleHealthReq).Methods("GET") + mx.NewRoute().Path(r.config.HealthPath).HandlerFunc(r.handleHealthReq) mx.NewRoute().Path(r.config.HealthPath + "/1.0").HandlerFunc(r.handleHealthReq).Methods("GET") if r.logsConsumer != nil { mx.NewRoute().Path(r.config.RawPath).HandlerFunc(r.handleRawReq) @@ -462,10 +462,7 @@ func (r *splunkReceiver) failRequest( func (r *splunkReceiver) handleHealthReq(writer http.ResponseWriter, _ *http.Request) { writer.Header().Add("Content-Type", "application/json") writer.WriteHeader(http.StatusOK) - _, err := writer.Write([]byte(responseHecHealthy)) - if err != nil { - return - } + writer.Write([]byte(responseHecHealthy)) } func initJSONResponse(s string) []byte { diff --git a/receiver/splunkhecreceiver/receiver_test.go b/receiver/splunkhecreceiver/receiver_test.go index 48c58e0948f4..9a43b2a7104e 100644 --- a/receiver/splunkhecreceiver/receiver_test.go +++ b/receiver/splunkhecreceiver/receiver_test.go @@ -1300,17 +1300,6 @@ func Test_splunkhecReceiver_healthCheck_success(t *testing.T) { assert.Equal(t, responseHecHealthy, body) }, }, - { - name: "incorrect_healthcheck_methods", - req: func() *http.Request { - req := httptest.NewRequest("POST", "http://localhost:0/services/collector/health", nil) - return req - }(), - assertResponse: func(t *testing.T, status int, body string) { - assert.Equal(t, http.StatusBadRequest, status) - assert.Equal(t, responseNoData, body) - }, - }, { name: "incorrect_healthcheck_methods_v1", req: func() *http.Request { From a0fe83161b481b9597a945095732f96b73816891 Mon Sep 17 00:00:00 2001 From: omrozowicz-splunk Date: Thu, 13 Apr 2023 09:08:23 +0200 Subject: [PATCH 5/5] fix linter error --- receiver/splunkhecreceiver/receiver.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/receiver/splunkhecreceiver/receiver.go b/receiver/splunkhecreceiver/receiver.go index f5ef7c9db9cf..69e63ee950c7 100644 --- a/receiver/splunkhecreceiver/receiver.go +++ b/receiver/splunkhecreceiver/receiver.go @@ -462,7 +462,7 @@ func (r *splunkReceiver) failRequest( func (r *splunkReceiver) handleHealthReq(writer http.ResponseWriter, _ *http.Request) { writer.Header().Add("Content-Type", "application/json") writer.WriteHeader(http.StatusOK) - writer.Write([]byte(responseHecHealthy)) + _, _ = writer.Write([]byte(responseHecHealthy)) } func initJSONResponse(s string) []byte {