Skip to content

fix(telemetry): reverse the order of HTTP and gateway user agents#19821

Open
parametalol wants to merge 4 commits intomasterfrom
michael/fix-grpc-ua-order
Open

fix(telemetry): reverse the order of HTTP and gateway user agents#19821
parametalol wants to merge 4 commits intomasterfrom
michael/fix-grpc-ua-order

Conversation

@parametalol
Copy link
Copy Markdown
Contributor

@parametalol parametalol commented Apr 3, 2026

Description

This PR refactors the user-agent calculation and btw changes the order of gRPC request user agents in the captured request parameters so that the original HTTP user agent goes first, and the grpc-gateway goes after.

Why important? Not much, but as the values for the header a stored in an array, a RequestParams reader might consider the first value to be the source user-agent.

It also fixes the Set method, that should delete the existing value if no new values are provided.

User-facing documentation

Testing and quality

  • the change is production ready: the change is GA, or otherwise the functionality is gated by a feature flag
  • CI results are inspected

Automated testing

  • added unit tests
  • added e2e tests
  • added regression tests
  • added compatibility tests
  • modified existing tests

How I validated my change

CI, manual.

@openshift-ci
Copy link
Copy Markdown

openshift-ci bot commented Apr 3, 2026

Skipping CI for Draft Pull Request.
If you want CI signal for your change, please convert it to an actual PR.
You can still manually trigger a test run with /test all

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 3, 2026

📝 Walkthrough

Walkthrough

The changes introduce clearer HTTP header manipulation semantics in the telemetry phonehome module. The Headers.Set() method now deletes existing values before setting new ones, making it a true replace operation. A new Headers.Add() method appends values to existing headers. The interceptor is updated to use Add() for appending gRPC User-Agent metadata instead of overwriting.

Changes

Cohort / File(s) Summary
Header Manipulation API
pkg/telemetry/phonehome/headers_multimap.go
Added Headers.Add() method to append header values. Modified Headers.Set() to delete existing key before setting (true replace semantics); deletes key when no values provided.
Interceptor Usage and Testing
pkg/telemetry/phonehome/interceptor.go, pkg/telemetry/phonehome/interceptor_test.go
Updated getGRPCRequestDetails to use Add() for appending User-Agent metadata instead of Set(). Test expectation adjusted to reflect new append-order behavior (["user", "gateway"] instead of ["gateway", "user"]).

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The PR title 'fix(telemetry): reverse the order of HTTP and gateway user agents' accurately and concisely describes the main change in the changeset.
Description check ✅ Passed The pull request description is well-structured and addresses all major sections of the template with clear explanations.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch michael/fix-grpc-ua-order

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 3, 2026

🚀 Build Images Ready

Images are ready for commit d008914. To use with deploy scripts:

export MAIN_IMAGE_TAG=4.11.x-583-gd008914bd3

@codecov
Copy link
Copy Markdown

codecov bot commented Apr 3, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 49.60%. Comparing base (118ca3c) to head (5f687ce).
⚠️ Report is 4 commits behind head on master.

Additional details and impacted files
@@            Coverage Diff             @@
##           master   #19821      +/-   ##
==========================================
- Coverage   49.60%   49.60%   -0.01%     
==========================================
  Files        2763     2763              
  Lines      208271   208335      +64     
==========================================
+ Hits       103309   103337      +28     
- Misses      97294    97331      +37     
+ Partials     7668     7667       -1     
Flag Coverage Δ
go-unit-tests 49.60% <100.00%> (-0.01%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@parametalol parametalol marked this pull request as ready for review April 3, 2026 20:03
@parametalol parametalol requested a review from a team April 3, 2026 20:04
Copy link
Copy Markdown
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 1 issue, and left some high level feedback:

  • In Headers.Set, you can avoid repeated http.Header(h) conversions and an unnecessary delete when no values are provided by early-returning on an empty values slice and reusing a local hdr := http.Header(h) alias.
  • The new Headers.Add helper is a good addition, but its comment is a bit vague; consider clarifying that it appends values to existing header entries (mirroring http.Header.Add) rather than replacing them, to make its behavior immediately clear to readers.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `Headers.Set`, you can avoid repeated `http.Header(h)` conversions and an unnecessary delete when no values are provided by early-returning on an empty `values` slice and reusing a local `hdr := http.Header(h)` alias.
- The new `Headers.Add` helper is a good addition, but its comment is a bit vague; consider clarifying that it appends values to existing header entries (mirroring `http.Header.Add`) rather than replacing them, to make its behavior immediately clear to readers.

## Individual Comments

### Comment 1
<location path="pkg/telemetry/phonehome/interceptor_test.go" line_range="181" />
<code_context>
 	rp := getGRPCRequestDetails(ctx, err, "ignored grpc method", "request")
 	s.Equal(http.StatusOK, rp.Code)
-	s.Equal([]string{"gateway", "user"}, rp.Headers.Get(userAgentHeaderKey))
+	s.Equal([]string{"user", "gateway"}, rp.Headers.Get(userAgentHeaderKey))
 	s.Nil(rp.UserID)
 	s.Equal("request", rp.GRPCReq)
</code_context>
<issue_to_address>
**suggestion (testing):** Add more test coverage for User-Agent combinations and edge cases

This assertion now covers the case where both HTTP and gateway User-Agents are present. To fully validate `getGRPCRequestDetails` behavior, please add tests for:

- Only HTTP User-Agent (no gRPC/gateway metadata)
- Only gRPC/gateway User-Agent (no HTTP header)
- Multiple metadata User-Agent values (verify ordering)
- Neither HTTP nor metadata User-Agent (no header set)

These will better protect against regressions in User-Agent construction across different request shapes.

Suggested implementation:

```golang
	rp := getGRPCRequestDetails(ctx, err, "ignored grpc method", "request")
	s.Equal(http.StatusOK, rp.Code)
	s.Equal([]string{"user", "gateway"}, rp.Headers.Get(userAgentHeaderKey))
	s.Nil(rp.UserID)
	s.Equal("request", rp.GRPCReq)
	s.Equal("/wrapped/http", rp.Path)

func TestGetGRPCRequestDetails_UserAgentVariants(t *testing.T) {
	type testCase struct {
		name string
		md   metadata.MD
		want []string
	}

	testCases := []testCase{
		{
			name: "only HTTP user agent (no gateway user agent)",
			md: metadata.Pairs(
				"grpcgateway-user-agent", "user",
			),
			want: []string{"user"},
		},
		{
			name: "only gateway user agent (no HTTP user agent)",
			md: metadata.Pairs(
				"user-agent", "gateway",
			),
			want: []string{"gateway"},
		},
		{
			name: "both HTTP and gateway user agents (HTTP first)",
			md: metadata.Pairs(
				"grpcgateway-user-agent", "user",
				"user-agent", "gateway",
			),
			want: []string{"user", "gateway"},
		},
		{
			name: "multiple HTTP and gateway user agents preserve ordering",
			md: metadata.Pairs(
				"grpcgateway-user-agent", "user1",
				"grpcgateway-user-agent", "user2",
				"user-agent", "gateway1",
				"user-agent", "gateway2",
			),
			// Expect all HTTP user agents first, in order, then all gateway user agents, in order.
			want: []string{"user1", "user2", "gateway1", "gateway2"},
		},
		{
			name: "no HTTP or gateway user agent (header not set)",
			md:   metadata.MD{},
			want: nil,
		},
	}

	for _, tc := range testCases {
		tc := tc
		t.Run(tc.name, func(t *testing.T) {
			t.Parallel()

			ctx := metadata.NewIncomingContext(context.Background(), tc.md)
			rp := getGRPCRequestDetails(ctx, nil, "ignored grpc method", "request")

			got := rp.Headers.Get(userAgentHeaderKey)
			if tc.want == nil {
				require.Nil(t, got)
				return
			}

			require.Equal(t, tc.want, got)
		})
	}
}

```

To compile and run these tests, ensure the following in `pkg/telemetry/phonehome/interceptor_test.go`:

1. The import block includes:
   - `testing`
   - `context`
   - `github.com/stretchr/testify/require`
   - `google.golang.org/grpc/metadata`

   For example:
   ```go
   import (
       "context"
       "testing"

       "github.com/stretchr/testify/require"
       "google.golang.org/grpc/metadata"
   )
   ```

2. The metadata keys used here (`"grpcgateway-user-agent"` for the original HTTP User-Agent and `"user-agent"` for the gateway/gRPC client User-Agent) must match what `getGRPCRequestDetails` actually reads. If the function uses different metadata keys, update the `metadata.Pairs` calls accordingly.

3. The expected ordering in the `"multiple HTTP and gateway user agents preserve ordering"` test (`HTTP UAs first, then gateway UAs`) should reflect the logic in `getGRPCRequestDetails`. If the function orders them differently, adjust the `want` slice to match the intended behavior.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@parametalol parametalol mentioned this pull request Apr 6, 2026
9 tasks
parametalol and others added 4 commits April 7, 2026 17:38
The grpc-gateway forwards the original HTTP User-Agent into gRPC
metadata, and the gRPC transport prepends its own. The previous
code appended all metadata values unconditionally, duplicating the
original. Now only metadata values not already present in the HTTP
headers are appended.

The test now simulates the real scenario where metadata contains
both the forwarded and transport User-Agent values.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Cover: HTTP-only, metadata-only, multiple metadata values with
ordering, and no User-Agent at all.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@parametalol parametalol force-pushed the michael/fix-grpc-ua-order branch from 5f687ce to d008914 Compare April 7, 2026 15:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant