Skip to content

Commit 54dded5

Browse files
authored
fix(splunk): treat HTTP 403 Forbidden as a permanent error (#40255)
#### Description - Splunk responses with a 403 typically indicate an authentication or authorization issue that is not likely to be resolved by retrying. This change ensures that the error is treated as permanent to avoid unnecessary retries. - This change is applied at `internal/splunk` level - There should be no breaking changes for `signalfxexporter`, `splunkhecexporter` #### Link to tracking issue Fixes #39037 #### Testing Added and updated tests for handling 403 permanent error #### Documentation https://docs.splunk.com/Documentation/Splunk/latest/Data/TroubleshootHTTPEventCollector#Possible_error_codes
1 parent bc0a7bc commit 54dded5

File tree

4 files changed

+89
-5
lines changed

4 files changed

+89
-5
lines changed
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# Use this changelog template to create an entry for release notes.
2+
3+
# One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix'
4+
change_type: bug_fix
5+
6+
# The name of the component, or a single word describing the area of concern, (e.g. filelogreceiver)
7+
component: internal/splunk
8+
9+
# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`).
10+
note: Treat HTTP 403 Forbidden as a permanent error.
11+
12+
# Mandatory: One or more tracking issues related to the change. You can use the PR number here if no issue exists.
13+
issues: [39037]
14+
15+
# (Optional) One or more lines of additional information to render under the primary note.
16+
# These lines will be padded with 2 spaces and then inserted directly into the document.
17+
# Use pipe (|) for multiline entries.
18+
subtext: |
19+
- Splunk responses with a 403 typically indicate an authentication or authorization issue that is not likely to be resolved by retrying.
20+
- This change ensures that the error is treated as permanent to avoid unnecessary retries.
21+
- This change is applicable to `splunkhecexporter`, `signalfxexporter`.
22+
23+
# If your change doesn't affect end users or the exported elements of any package,
24+
# you should instead start your pull request title with [chore] or use the "Skip Changelog" label.
25+
# Optional: The change log or logs in which this entry should be included.
26+
# e.g. '[user]' or '[user, api]'
27+
# Include 'user' if the change is relevant to end users.
28+
# Include 'api' if there is a change to a library API.
29+
# Default: '[user]'
30+
change_logs: []

exporter/signalfxexporter/exporter_test.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -220,14 +220,15 @@ func TestConsumeMetrics(t *testing.T) {
220220

221221
if tt.wantErr {
222222
assert.Error(t, err)
223-
assert.EqualError(t, err, errMsg)
223+
assert.ErrorContains(t, err, errMsg)
224224
return
225225
}
226226

227227
if tt.wantPermanentErr {
228+
errMsg = "Permanent error: " + errMsg
228229
assert.Error(t, err)
229230
assert.True(t, consumererror.IsPermanent(err))
230-
assert.ErrorContains(t, err, errMsg)
231+
assert.EqualError(t, err, errMsg)
231232
return
232233
}
233234

@@ -2085,7 +2086,7 @@ func TestConsumeMixedMetrics(t *testing.T) {
20852086

20862087
if tt.wantErr {
20872088
assert.Error(t, err)
2088-
assert.EqualError(t, err, errMsg)
2089+
assert.ErrorContains(t, err, errMsg)
20892090
return
20902091
}
20912092

exporter/splunkhecexporter/client_test.go

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1324,6 +1324,59 @@ func TestErrorReceived(t *testing.T) {
13241324
assert.EqualError(t, err, errMsg)
13251325
}
13261326

1327+
func TestErrorReceivedForbidden(t *testing.T) {
1328+
rr := make(chan receivedRequest)
1329+
capture := capturingData{receivedRequest: rr, statusCode: 403}
1330+
listener, err := net.Listen("tcp", "127.0.0.1:0")
1331+
if err != nil {
1332+
panic(err)
1333+
}
1334+
s := &http.Server{
1335+
Handler: &capture,
1336+
ReadHeaderTimeout: 20 * time.Second,
1337+
}
1338+
defer s.Close()
1339+
go func() {
1340+
if e := s.Serve(listener); e != http.ErrServerClosed {
1341+
assert.NoError(t, e)
1342+
}
1343+
}()
1344+
1345+
factory := NewFactory()
1346+
cfg := factory.CreateDefaultConfig().(*Config)
1347+
cfg.Endpoint = "http://" + listener.Addr().String() + "/services/collector"
1348+
// Disable QueueSettings to ensure that we execute the request when calling ConsumeTraces
1349+
// otherwise we will not see the error.
1350+
cfg.QueueSettings.Enabled = false
1351+
// Disable retries to not wait too much time for the return error.
1352+
cfg.Enabled = false
1353+
cfg.DisableCompression = true
1354+
cfg.Token = "1234-1234"
1355+
1356+
params := exportertest.NewNopSettings(metadata.Type)
1357+
exporter, err := factory.CreateTraces(context.Background(), params, cfg)
1358+
assert.NoError(t, err)
1359+
assert.NoError(t, exporter.Start(context.Background(), componenttest.NewNopHost()))
1360+
defer func() {
1361+
assert.NoError(t, exporter.Shutdown(context.Background()))
1362+
}()
1363+
1364+
td := createTraceData(1, 3)
1365+
1366+
err = exporter.ConsumeTraces(context.Background(), td)
1367+
select {
1368+
case <-rr:
1369+
case <-time.After(5 * time.Second):
1370+
t.Fatal("Should have received request")
1371+
}
1372+
errMsg := fmt.Sprintf("Permanent error: HTTP \"/services/collector\" %d %q",
1373+
http.StatusForbidden,
1374+
http.StatusText(http.StatusForbidden),
1375+
)
1376+
assert.EqualError(t, err, errMsg)
1377+
assert.True(t, consumererror.IsPermanent(err))
1378+
}
1379+
13271380
func TestInvalidLogs(t *testing.T) {
13281381
config := NewFactory().CreateDefaultConfig().(*Config)
13291382
config.DisableCompression = false
@@ -1399,7 +1452,7 @@ func TestHeartbeatStartupFailed(t *testing.T) {
13991452
assert.NoError(t, err)
14001453
assert.EqualError(t,
14011454
exporter.Start(context.Background(), componenttest.NewNopHost()),
1402-
fmt.Sprintf("%s: heartbeat on startup failed: HTTP \"/services/collector\" 403 \"Forbidden\"",
1455+
fmt.Sprintf("%s: heartbeat on startup failed: Permanent error: HTTP \"/services/collector\" 403 \"Forbidden\"",
14031456
params.ID.String(),
14041457
),
14051458
)

internal/splunk/httprequest.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ func HandleHTTPCode(resp *http.Response) error {
4343
// Indicate to our caller to pause for the specified number of seconds.
4444
err = exporterhelper.NewThrottleRetry(err, time.Duration(retryAfter)*time.Second)
4545
// Check for permanent errors.
46-
case http.StatusBadRequest, http.StatusUnauthorized:
46+
case http.StatusBadRequest, http.StatusUnauthorized, http.StatusForbidden:
4747
err = consumererror.NewPermanent(err)
4848
}
4949

0 commit comments

Comments
 (0)