Skip to content

Commit

Permalink
continuous-test: Make the User-Agent header for the Mimir client conf… (
Browse files Browse the repository at this point in the history
#9338)

* continuous-test: Make the User-Agent header for the Mimir client configurable

* Update CHANGELOG.md

* Run make reference-help
  • Loading branch information
leizor authored Sep 20, 2024
1 parent 922a8c5 commit 5048ed0
Show file tree
Hide file tree
Showing 5 changed files with 118 additions and 108 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,7 @@
* [ENHANCEMENT] Add histograms to measure latency of read and write requests. #8583
* [ENHANCEMENT] Log successful test runs in addition to failed test runs. #8817
* [ENHANCEMENT] Series emitted by continuous-test now distribute more uniformly across ingesters. #9218 #9243
* [ENHANCEMENT] Configure `User-Agent` header for the Mimir client via `-tests.client.user-agent`. #9338
* [BUGFIX] Initialize test result metrics to 0 at startup so that alerts can correctly identify the first failure after startup. #8630

### Query-tee
Expand Down
2 changes: 2 additions & 0 deletions cmd/mimir/help-all.txt.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -3031,6 +3031,8 @@ Usage of ./cmd/mimir/mimir:
The username to use for HTTP bearer authentication. (mutually exclusive with bearer-token flag)
-tests.bearer-token string
The bearer token to use for HTTP bearer authentication. (mutually exclusive with basic-auth flags)
-tests.client.user-agent string
The value the Mimir client should send in the User-Agent header. (default "mimir-continuous-test")
-tests.read-endpoint string
The base endpoint on the read path. The URL should have no trailing slash. The specific API path is appended by the tool to the URL, for example /api/v1/query_range for range query API, so the configured URL must not include it.
-tests.read-timeout duration
Expand Down
2 changes: 2 additions & 0 deletions cmd/mimir/help.txt.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -829,6 +829,8 @@ Usage of ./cmd/mimir/mimir:
The username to use for HTTP bearer authentication. (mutually exclusive with bearer-token flag)
-tests.bearer-token string
The bearer token to use for HTTP bearer authentication. (mutually exclusive with basic-auth flags)
-tests.client.user-agent string
The value the Mimir client should send in the User-Agent header. (default "mimir-continuous-test")
-tests.read-endpoint string
The base endpoint on the read path. The URL should have no trailing slash. The specific API path is appended by the tool to the URL, for example /api/v1/query_range for range query API, so the configured URL must not include it.
-tests.read-timeout duration
Expand Down
13 changes: 10 additions & 3 deletions pkg/continuoustest/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,9 @@ import (
)

const (
maxErrMsgLen = 256
defaultTenant = "anonymous"
maxErrMsgLen = 256
defaultTenant = "anonymous"
defaultUserAgent = "mimir-continuous-test"
)

// MimirClient is the interface implemented by a client used to interact with Mimir.
Expand Down Expand Up @@ -56,6 +57,7 @@ type ClientConfig struct {
ReadTimeout time.Duration

RequestDebug bool
UserAgent string
}

func (cfg *ClientConfig) RegisterFlags(f *flag.FlagSet) {
Expand All @@ -72,6 +74,7 @@ func (cfg *ClientConfig) RegisterFlags(f *flag.FlagSet) {
f.Var(&cfg.ReadBaseEndpoint, "tests.read-endpoint", "The base endpoint on the read path. The URL should have no trailing slash. The specific API path is appended by the tool to the URL, for example /api/v1/query_range for range query API, so the configured URL must not include it.")
f.DurationVar(&cfg.ReadTimeout, "tests.read-timeout", 60*time.Second, "The timeout for a single read request.")
f.BoolVar(&cfg.RequestDebug, "tests.send-chunks-debugging-header", false, "Request debugging on the server side via header.")
f.StringVar(&cfg.UserAgent, "tests.client.user-agent", defaultUserAgent, "The value the Mimir client should send in the User-Agent header.")
}

type Client struct {
Expand All @@ -93,6 +96,7 @@ func NewClient(cfg ClientConfig, logger log.Logger) (*Client, error) {
bearerToken: cfg.BearerToken,
rt: instrumentation.TracerTransport{},
requestDebug: cfg.RequestDebug,
userAgent: cfg.UserAgent,
}

// Ensure the required config has been set.
Expand Down Expand Up @@ -262,6 +266,7 @@ type clientRoundTripper struct {
bearerToken string
rt http.RoundTripper
requestDebug bool
userAgent string
}

// RoundTrip add the tenant ID header required by Mimir.
Expand All @@ -286,7 +291,9 @@ func (rt *clientRoundTripper) RoundTrip(req *http.Request) (*http.Response, erro
req.Header.Set("X-Scope-OrgID", rt.tenantID)
}

req.Header.Set("User-Agent", "mimir-continuous-test")
if rt.userAgent != "" {
req.Header.Set("User-Agent", rt.userAgent)
}

if lvl, ok := querierapi.ReadConsistencyLevelFromContext(req.Context()); ok {
req.Header.Add(querierapi.ReadConsistencyHeader, lvl)
Expand Down
208 changes: 103 additions & 105 deletions pkg/continuoustest/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ package continuoustest
import (
"compress/gzip"
"context"
"encoding/base64"
"fmt"
"io"
"net/http"
"net/http/httptest"
Expand Down Expand Up @@ -310,111 +312,107 @@ func TestClient_QueryHeaders(t *testing.T) {
}))
t.Cleanup(server.Close)

cfg := ClientConfig{}
flagext.DefaultValues(&cfg)
require.NoError(t, cfg.WriteBaseEndpoint.Set(server.URL))
require.NoError(t, cfg.ReadBaseEndpoint.Set(server.URL))

c, err := NewClient(cfg, log.NewNopLogger())
require.NoError(t, err)

ctx := context.Background()

t.Run("default tenant header is used without auth", func(t *testing.T) {
receivedRequests = nil

_, err := c.Query(ctx, "up", time.Unix(0, 0))
require.NoError(t, err)

require.Len(t, receivedRequests, 1)
assert.Equal(t, "anonymous", receivedRequests[0].Header.Get("X-Scope-OrgID"))
assert.Empty(t, receivedRequests[0].Header.Get("Authorization"))
})

t.Run("tenant header is not used when basic auth is used", func(t *testing.T) {
cfg = ClientConfig{}
flagext.DefaultValues(&cfg)
require.NoError(t, cfg.WriteBaseEndpoint.Set(server.URL))
require.NoError(t, cfg.ReadBaseEndpoint.Set(server.URL))
cfg.BasicAuthUser = "mimir-user"
cfg.BasicAuthPassword = "guest"

c, err := NewClient(cfg, log.NewNopLogger())
require.NoError(t, err)
ctx := context.Background()
receivedRequests = nil

_, err = c.Query(ctx, "up", time.Unix(0, 0))
require.NoError(t, err)

require.Len(t, receivedRequests, 1)
assert.Empty(t, receivedRequests[0].Header.Get("X-Scope-OrgID"))
assert.NotEmpty(t, receivedRequests[0].Header.Get("Authorization"))
})

t.Run("tenant header is not used when bearer token used", func(t *testing.T) {
cfg = ClientConfig{}
flagext.DefaultValues(&cfg)
require.NoError(t, cfg.WriteBaseEndpoint.Set(server.URL))
require.NoError(t, cfg.ReadBaseEndpoint.Set(server.URL))
cfg.BearerToken = "mimir-token"

c, err := NewClient(cfg, log.NewNopLogger())
require.NoError(t, err)
ctx := context.Background()
receivedRequests = nil

_, err = c.Query(ctx, "up", time.Unix(0, 0))
require.NoError(t, err)

require.Len(t, receivedRequests, 1)
assert.Empty(t, receivedRequests[0].Header.Get("X-Scope-OrgID"))
assert.NotEmpty(t, receivedRequests[0].Header.Get("Authorization"))
})

t.Run("tenant header can be used as well as basic auth", func(t *testing.T) {
cfg = ClientConfig{}
flagext.DefaultValues(&cfg)
require.NoError(t, cfg.WriteBaseEndpoint.Set(server.URL))
require.NoError(t, cfg.ReadBaseEndpoint.Set(server.URL))
cfg.BasicAuthUser = "mimir-user"
cfg.BasicAuthPassword = "guest"
cfg.TenantID = "tenant1"

c, err := NewClient(cfg, log.NewNopLogger())
require.NoError(t, err)
ctx := context.Background()
receivedRequests = nil

_, err = c.Query(ctx, "up", time.Unix(0, 0))
require.NoError(t, err)

require.Len(t, receivedRequests, 1)
assert.Equal(t, "tenant1", receivedRequests[0].Header.Get("X-Scope-OrgID"))
assert.NotEmpty(t, receivedRequests[0].Header.Get("Authorization"))
})

t.Run("tenant header can be used as well as bearer token", func(t *testing.T) {
cfg = ClientConfig{}
flagext.DefaultValues(&cfg)
require.NoError(t, cfg.WriteBaseEndpoint.Set(server.URL))
require.NoError(t, cfg.ReadBaseEndpoint.Set(server.URL))
cfg.BearerToken = "mimir-token"
cfg.TenantID = "tenant1"

c, err := NewClient(cfg, log.NewNopLogger())
require.NoError(t, err)
ctx := context.Background()
receivedRequests = nil

_, err = c.Query(ctx, "up", time.Unix(0, 0))
require.NoError(t, err)

require.Len(t, receivedRequests, 1)
assert.Equal(t, "tenant1", receivedRequests[0].Header.Get("X-Scope-OrgID"))
assert.NotEmpty(t, receivedRequests[0].Header.Get("Authorization"))
})

basicAuth := func(user, pass string) string {
return fmt.Sprintf("Basic %s", base64.StdEncoding.EncodeToString([]byte(user+":"+pass)))
}

testCases := map[string]struct {
cfgMutator func(*ClientConfig)

// There may be other headers on the resulting request, but we'll only check these:
expectedHeaders map[string]string
expectedEmptyHeaders []string
}{
"default tenant header is used without auth": {
expectedHeaders: map[string]string{
"X-Scope-OrgID": "anonymous",
},
expectedEmptyHeaders: []string{"Authorization"},
},
"tenant header is not used when basic auth is used": {
cfgMutator: func(cfg *ClientConfig) {
cfg.BasicAuthUser = "mimir-user"
cfg.BasicAuthPassword = "guest"
},
expectedHeaders: map[string]string{
"Authorization": basicAuth("mimir-user", "guest"),
},
expectedEmptyHeaders: []string{"X-Scope-OrgID"},
},
"tenant header is not used when bearer token used": {
cfgMutator: func(cfg *ClientConfig) {
cfg.BearerToken = "mimir-token"
},
expectedHeaders: map[string]string{
"Authorization": "Bearer mimir-token",
},
expectedEmptyHeaders: []string{"X-Scope-OrgID"},
},
"tenant header can be used as well as basic auth": {
cfgMutator: func(cfg *ClientConfig) {
cfg.BasicAuthUser = "mimir-user"
cfg.BasicAuthPassword = "guest"
cfg.TenantID = "tenant1"
},
expectedHeaders: map[string]string{
"X-Scope-OrgID": "tenant1",
"Authorization": basicAuth("mimir-user", "guest"),
},
},
"tenant header can be used as well as bearer token": {
cfgMutator: func(cfg *ClientConfig) {
cfg.BearerToken = "mimir-token"
cfg.TenantID = "tenant1"
},
expectedHeaders: map[string]string{
"X-Scope-OrgID": "tenant1",
"Authorization": "Bearer mimir-token",
},
},
"default user agent": {
expectedHeaders: map[string]string{
"User-Agent": "mimir-continuous-test",
},
},
"non-default user agent": {
cfgMutator: func(cfg *ClientConfig) {
cfg.UserAgent = "other-user-agent"
},
expectedHeaders: map[string]string{
"User-Agent": "other-user-agent",
},
},
}

for testName, tc := range testCases {
t.Run(testName, func(t *testing.T) {
receivedRequests = nil

cfg := ClientConfig{}
flagext.DefaultValues(&cfg)
require.NoError(t, cfg.WriteBaseEndpoint.Set(server.URL))
require.NoError(t, cfg.ReadBaseEndpoint.Set(server.URL))
if tc.cfgMutator != nil {
tc.cfgMutator(&cfg)
}

c, err := NewClient(cfg, log.NewNopLogger())
require.NoError(t, err)

ctx := context.Background()

_, err = c.Query(ctx, "up", time.Unix(0, 0))
require.NoError(t, err)

require.Len(t, receivedRequests, 1)
for k, v := range tc.expectedHeaders {
require.Equal(t, v, receivedRequests[0].Header.Get(k))
}
for _, k := range tc.expectedEmptyHeaders {
require.Empty(t, receivedRequests[0].Header.Get(k))
}
})
}
}

// ClientMock mocks MimirClient.
Expand Down

0 comments on commit 5048ed0

Please sign in to comment.