diff --git a/CHANGELOG.md b/CHANGELOG.md index a2695830e6..87d8c82db3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,11 +10,6 @@ We use *breaking :warning:* to mark changes that are not backward compatible (re ## Unreleased -### Fixed - -- [#4663](https://github.com/thanos-io/thanos/pull/4663) Fetcher: Fix discovered data races -- [#4753](https://github.com/thanos-io/thanos/pull/4753) Store: valide block sync concurrency parameter - ### Added - [#4680](https://github.com/thanos-io/thanos/pull/4680) Query: add `exemplar.partial-response` flag to control partial response. @@ -24,6 +19,9 @@ We use *breaking :warning:* to mark changes that are not backward compatible (re ### Fixed - [#4508](https://github.com/thanos-io/thanos/pull/4508) Adjust and rename `ThanosSidecarUnhealthy` to `ThanosSidecarNoConnectionToStartedPrometheus`; Remove `ThanosSidecarPrometheusDown` alert; Remove unused `thanos_sidecar_last_heartbeat_success_time_seconds` metrics. +- [#4663](https://github.com/thanos-io/thanos/pull/4663) Fetcher: Fix discovered data races. +- [#4754](https://github.com/thanos-io/thanos/pull/4754) Query: Fix possible panic on stores endpoint. +- [#4753](https://github.com/thanos-io/thanos/pull/4753) Store: validate block sync concurrency parameter ## [v0.23.1](https://github.com/thanos-io/thanos/tree/release-0.23) - 2021.10.1 diff --git a/cmd/thanos/query.go b/cmd/thanos/query.go index 8b280229b2..373cd57913 100644 --- a/cmd/thanos/query.go +++ b/cmd/thanos/query.go @@ -569,7 +569,7 @@ func runQuery( api := v1.NewQueryAPI( logger, - endpoints, + endpoints.GetEndpointStatus, engineFactory(promql.NewEngine, engineOpts, dynamicLookbackDelta), queryableCreator, // NOTE: Will share the same replica label as the query for now. diff --git a/pkg/api/query/v1.go b/pkg/api/query/v1.go index 4f3866b62b..fd9ee99b68 100644 --- a/pkg/api/query/v1.go +++ b/pkg/api/query/v1.go @@ -93,8 +93,8 @@ type QueryAPI struct { enableExemplarPartialResponse bool disableCORS bool - replicaLabels []string - endpointSet *query.EndpointSet + replicaLabels []string + endpointStatus func() []query.EndpointStatus defaultRangeQueryStep time.Duration defaultInstantQueryMaxSourceResolution time.Duration @@ -106,7 +106,7 @@ type QueryAPI struct { // NewQueryAPI returns an initialized QueryAPI type. func NewQueryAPI( logger log.Logger, - endpointSet *query.EndpointSet, + endpointStatus func() []query.EndpointStatus, qe func(int64) *promql.Engine, c query.QueryableCreator, ruleGroups rules.UnaryClient, @@ -146,7 +146,7 @@ func NewQueryAPI( enableMetricMetadataPartialResponse: enableMetricMetadataPartialResponse, enableExemplarPartialResponse: enableExemplarPartialResponse, replicaLabels: replicaLabels, - endpointSet: endpointSet, + endpointStatus: endpointStatus, defaultRangeQueryStep: defaultRangeQueryStep, defaultInstantQueryMaxSourceResolution: defaultInstantQueryMaxSourceResolution, defaultMetadataTimeRange: defaultMetadataTimeRange, @@ -715,7 +715,11 @@ func (qapi *QueryAPI) labelNames(r *http.Request) (interface{}, []error, *api.Ap func (qapi *QueryAPI) stores(_ *http.Request) (interface{}, []error, *api.ApiError) { statuses := make(map[string][]query.EndpointStatus) - for _, status := range qapi.endpointSet.GetEndpointStatus() { + for _, status := range qapi.endpointStatus() { + // Don't consider an endpoint if we cannot retrieve component type. + if status.ComponentType == nil { + continue + } statuses[status.ComponentType.String()] = append(statuses[status.ComponentType.String()], status) } return statuses, nil, nil diff --git a/pkg/api/query/v1_test.go b/pkg/api/query/v1_test.go index a9f0648f42..218498fe81 100644 --- a/pkg/api/query/v1_test.go +++ b/pkg/api/query/v1_test.go @@ -1201,6 +1201,93 @@ func TestMetadataEndpoints(t *testing.T) { } } +func TestStoresEndpoint(t *testing.T) { + apiWithNotEndpoints := &QueryAPI{ + endpointStatus: func() []query.EndpointStatus { + return []query.EndpointStatus{} + }, + } + apiWithValidEndpoints := &QueryAPI{ + endpointStatus: func() []query.EndpointStatus { + return []query.EndpointStatus{ + { + Name: "endpoint-1", + ComponentType: component.Store, + }, + { + Name: "endpoint-2", + ComponentType: component.Store, + }, + { + Name: "endpoint-3", + ComponentType: component.Sidecar, + }, + } + }, + } + apiWithInvalidEndpoint := &QueryAPI{ + endpointStatus: func() []query.EndpointStatus { + return []query.EndpointStatus{ + { + Name: "endpoint-1", + ComponentType: component.Store, + }, + { + Name: "endpoint-2", + }, + } + }, + } + + testCases := []endpointTestCase{ + { + endpoint: apiWithNotEndpoints.stores, + method: http.MethodGet, + response: map[string][]query.EndpointStatus{}, + }, + { + endpoint: apiWithValidEndpoints.stores, + method: http.MethodGet, + response: map[string][]query.EndpointStatus{ + "store": { + { + Name: "endpoint-1", + ComponentType: component.Store, + }, + { + Name: "endpoint-2", + ComponentType: component.Store, + }, + }, + "sidecar": { + { + Name: "endpoint-3", + ComponentType: component.Sidecar, + }, + }, + }, + }, + { + endpoint: apiWithInvalidEndpoint.stores, + method: http.MethodGet, + response: map[string][]query.EndpointStatus{ + "store": { + { + Name: "endpoint-1", + ComponentType: component.Store, + }, + }, + }, + }, + } + + for i, test := range testCases { + if ok := testEndpoint(t, test, strings.TrimSpace(fmt.Sprintf("#%d %s", i, test.query.Encode())), reflect.DeepEqual); !ok { + return + } + } +} + func TestParseTime(t *testing.T) { ts, err := time.Parse(time.RFC3339Nano, "2015-06-03T13:21:58.555Z") if err != nil {