-
Notifications
You must be signed in to change notification settings - Fork 2.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Query: Forward tenant information via StoreAPI #6530
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -61,6 +61,7 @@ import ( | |
"github.com/thanos-io/thanos/pkg/store/storepb" | ||
"github.com/thanos-io/thanos/pkg/targets" | ||
"github.com/thanos-io/thanos/pkg/targets/targetspb" | ||
"github.com/thanos-io/thanos/pkg/tenancy" | ||
"github.com/thanos-io/thanos/pkg/tracing" | ||
) | ||
|
||
|
@@ -161,6 +162,10 @@ type QueryAPI struct { | |
queryRangeHist prometheus.Histogram | ||
|
||
seriesStatsAggregator seriesQueryPerformanceMetricsAggregator | ||
|
||
tenantHeader string | ||
defaultTenant string | ||
tenantCertField string | ||
} | ||
|
||
type seriesQueryPerformanceMetricsAggregator interface { | ||
|
@@ -196,6 +201,9 @@ func NewQueryAPI( | |
gate gate.Gate, | ||
statsAggregator seriesQueryPerformanceMetricsAggregator, | ||
reg *prometheus.Registry, | ||
tenantHeader string, | ||
defaultTenant string, | ||
tenantCertField string, | ||
) *QueryAPI { | ||
if statsAggregator == nil { | ||
statsAggregator = &store.NoopSeriesStatsAggregator{} | ||
|
@@ -226,6 +234,9 @@ func NewQueryAPI( | |
defaultMetadataTimeRange: defaultMetadataTimeRange, | ||
disableCORS: disableCORS, | ||
seriesStatsAggregator: statsAggregator, | ||
tenantHeader: tenantHeader, | ||
defaultTenant: defaultTenant, | ||
tenantCertField: tenantCertField, | ||
|
||
queryRangeHist: promauto.With(reg).NewHistogram(prometheus.HistogramOpts{ | ||
Name: "thanos_query_range_requested_timespan_duration_seconds", | ||
|
@@ -505,6 +516,13 @@ func (qapi *QueryAPI) query(r *http.Request) (interface{}, []error, *api.ApiErro | |
lookbackDelta = lookbackDeltaFromReq | ||
} | ||
|
||
tenant, err := tenancy.GetTenantFromHTTP(r, qapi.tenantHeader, qapi.defaultTenant, qapi.tenantCertField) | ||
if err != nil { | ||
apiErr = &api.ApiError{Typ: api.ErrorBadData, Err: err} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe we shouldn't return error here, and instead assume best-effort and just fallback to default-tenant? ditto for rest of the places this is implemented in. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't believe this should break any existing setups (unless someone is already sending tenant-headers which are currently just being ignored). In case the tenant header is empty/non-existing we'll use the default one. However, if we get a tenant which is invalid (i.e a malicious one per #5969 ), or if one specifically expects a tenant to be found via the certificate, but none is, then we do error out. At least in the case of a malicious tenant being sent, I think that is sensible? I'm not sure in the case of the certificate, I suspect that one wouldn't have a cert without the configured field, though I am not sure. I re-used the receive implementation here, so the behavior here should be the same. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You're right, I forgot how GetTenantFromHTTP worked, it's fine then. |
||
return nil, nil, apiErr, func() {} | ||
} | ||
ctx = context.WithValue(ctx, tenancy.TenantKey, tenant) | ||
|
||
// We are starting promQL tracing span here, because we have no control over promQL code. | ||
span, ctx := tracing.StartSpan(ctx, "promql_instant_query") | ||
defer span.Finish() | ||
|
@@ -665,6 +683,13 @@ func (qapi *QueryAPI) queryRange(r *http.Request) (interface{}, []error, *api.Ap | |
lookbackDelta = lookbackDeltaFromReq | ||
} | ||
|
||
tenant, err := tenancy.GetTenantFromHTTP(r, qapi.tenantHeader, qapi.defaultTenant, qapi.tenantCertField) | ||
if err != nil { | ||
apiErr = &api.ApiError{Typ: api.ErrorBadData, Err: err} | ||
return nil, nil, apiErr, func() {} | ||
} | ||
ctx = context.WithValue(ctx, tenancy.TenantKey, tenant) | ||
|
||
// Record the query range requested. | ||
qapi.queryRangeHist.Observe(end.Sub(start).Seconds()) | ||
|
||
|
@@ -770,6 +795,13 @@ func (qapi *QueryAPI) labelValues(r *http.Request) (interface{}, []error, *api.A | |
matcherSets = append(matcherSets, matchers) | ||
} | ||
|
||
tenant, err := tenancy.GetTenantFromHTTP(r, qapi.tenantHeader, qapi.defaultTenant, qapi.tenantCertField) | ||
if err != nil { | ||
apiErr = &api.ApiError{Typ: api.ErrorBadData, Err: err} | ||
return nil, nil, apiErr, func() {} | ||
} | ||
ctx = context.WithValue(ctx, tenancy.TenantKey, tenant) | ||
|
||
q, err := qapi.queryableCreate( | ||
true, | ||
nil, | ||
|
@@ -866,6 +898,13 @@ func (qapi *QueryAPI) series(r *http.Request) (interface{}, []error, *api.ApiErr | |
return nil, nil, apiErr, func() {} | ||
} | ||
|
||
tenant, err := tenancy.GetTenantFromHTTP(r, qapi.tenantHeader, qapi.defaultTenant, "") | ||
if err != nil { | ||
apiErr = &api.ApiError{Typ: api.ErrorBadData, Err: err} | ||
return nil, nil, apiErr, func() {} | ||
} | ||
ctx := context.WithValue(r.Context(), tenancy.TenantKey, tenant) | ||
|
||
q, err := qapi.queryableCreate( | ||
enableDedup, | ||
replicaLabels, | ||
|
@@ -876,7 +915,7 @@ func (qapi *QueryAPI) series(r *http.Request) (interface{}, []error, *api.ApiErr | |
true, | ||
nil, | ||
query.NoopSeriesStatsReporter, | ||
).Querier(r.Context(), timestamp.FromTime(start), timestamp.FromTime(end)) | ||
).Querier(ctx, timestamp.FromTime(start), timestamp.FromTime(end)) | ||
|
||
if err != nil { | ||
return nil, nil, &api.ApiError{Typ: api.ErrorExec, Err: err}, func() {} | ||
|
@@ -926,6 +965,13 @@ func (qapi *QueryAPI) labelNames(r *http.Request) (interface{}, []error, *api.Ap | |
matcherSets = append(matcherSets, matchers) | ||
} | ||
|
||
tenant, err := tenancy.GetTenantFromHTTP(r, qapi.tenantHeader, qapi.defaultTenant, "") | ||
if err != nil { | ||
apiErr = &api.ApiError{Typ: api.ErrorBadData, Err: err} | ||
return nil, nil, apiErr, func() {} | ||
} | ||
ctx := context.WithValue(r.Context(), tenancy.TenantKey, tenant) | ||
|
||
q, err := qapi.queryableCreate( | ||
true, | ||
nil, | ||
|
@@ -936,7 +982,7 @@ func (qapi *QueryAPI) labelNames(r *http.Request) (interface{}, []error, *api.Ap | |
true, | ||
nil, | ||
query.NoopSeriesStatsReporter, | ||
).Querier(r.Context(), timestamp.FromTime(start), timestamp.FromTime(end)) | ||
).Querier(ctx, timestamp.FromTime(start), timestamp.FromTime(end)) | ||
if err != nil { | ||
return nil, nil, &api.ApiError{Typ: api.ErrorExec, Err: err}, func() {} | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -58,6 +58,7 @@ import ( | |
"github.com/thanos-io/thanos/pkg/store/labelpb" | ||
"github.com/thanos-io/thanos/pkg/store/storepb" | ||
"github.com/thanos-io/thanos/pkg/strutil" | ||
"github.com/thanos-io/thanos/pkg/tenancy" | ||
"github.com/thanos-io/thanos/pkg/tracing" | ||
) | ||
|
||
|
@@ -1229,6 +1230,12 @@ func (s *BucketStore) Series(req *storepb.SeriesRequest, srv storepb.Store_Serie | |
defer s.queryGate.Done() | ||
} | ||
|
||
tenant, err := tenancy.GetTenantFromGRPCMetadata(srv.Context()) | ||
if err != nil { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How is the tenant value actually used in store gateway? I see it is only used in the logger rn. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That's right for now. Splitting up the full implementation in order to not end up with a very large PR. There's an action plan at the end of the proposal. This PR corresponds to step one of the query component. |
||
level.Warn(s.logger).Log("msg", err) | ||
} | ||
level.Debug(s.logger).Log("msg", "Tenant for Series request", "tenant", tenant) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These debug and warning logs seem too verbose, no? Can we remove those logs in next pr? |
||
|
||
matchers, err := storepb.MatchersToPromMatchers(req.Matchers...) | ||
if err != nil { | ||
return status.Error(codes.InvalidArgument, err.Error()) | ||
|
@@ -1478,6 +1485,12 @@ func (s *BucketStore) LabelNames(ctx context.Context, req *storepb.LabelNamesReq | |
return nil, status.Error(codes.InvalidArgument, errors.Wrap(err, "translate request labels matchers").Error()) | ||
} | ||
|
||
tenant, err := tenancy.GetTenantFromGRPCMetadata(ctx) | ||
if err != nil { | ||
level.Warn(s.logger).Log("msg", err) | ||
} | ||
level.Debug(s.logger).Log("msg", "Tenant for LabelNames request", "tenant", tenant) | ||
|
||
resHints := &hintspb.LabelNamesResponseHints{} | ||
|
||
var reqBlockMatchers []*labels.Matcher | ||
|
@@ -1666,6 +1679,12 @@ func (s *BucketStore) LabelValues(ctx context.Context, req *storepb.LabelValuesR | |
return nil, status.Error(codes.InvalidArgument, errors.Wrap(err, "translate request labels matchers").Error()) | ||
} | ||
|
||
tenant, err := tenancy.GetTenantFromGRPCMetadata(ctx) | ||
if err != nil { | ||
level.Warn(s.logger).Log("msg", err) | ||
GiedriusS marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
level.Debug(s.logger).Log("msg", "Tenant for LabelValues request", "tenant", tenant) | ||
|
||
resHints := &hintspb.LabelValuesResponseHints{} | ||
|
||
g, gctx := errgroup.WithContext(ctx) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if we should have a tenancy config file instead of a bunch of command line parameters? There are a bunch of tenancy features that people would like to have so it would be easier to extend a config file in the future
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, we discussed this during proposal review as well. I believe we decided to follow same Receive convention? Feel free to correct me if I'm wrong cc: @douglascamata
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Link to the specific discussion (I think): #6320 (comment)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that for this flag specifically, a configuration file won't be necessary.
For all the per-tenant configuration that it enables (i.e. different query limits, concurrency, etc) should be managed through a file, like we do in Thanos Receive for
--receive.limits-config
and--receive.tenant-header
. This keeps us consistent.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's a potential to enable configuration of all the CLI args in a file. But those CLI args that also should have files as content complicate this path.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm all in for a migration of all tenant configuration to a single file across all components. This would have to be well planned, especially thinking about backwards compatibility. 🤔