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: diff --git a/receiver/splunkhecreceiver/receiver.go b/receiver/splunkhecreceiver/receiver.go index b9752346da3a..69e63ee950c7 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}` @@ -203,6 +204,7 @@ 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 + "/1.0").HandlerFunc(r.handleHealthReq).Methods("GET") if r.logsConsumer != nil { mx.NewRoute().Path(r.config.RawPath).HandlerFunc(r.handleRawReq) } @@ -458,7 +460,9 @@ 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) + _, _ = writer.Write([]byte(responseHecHealthy)) } func initJSONResponse(s string) []byte { diff --git a/receiver/splunkhecreceiver/receiver_test.go b/receiver/splunkhecreceiver/receiver_test.go index 1c1db2ec2eea..9a43b2a7104e 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) } @@ -1268,3 +1268,74 @@ 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_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) + }) + } +}