Refuse an /events aggregate that also asks for distinct_resources
ci/woodpecker/pr/build Pipeline was successful
ci/woodpecker/pr/test Pipeline was successful
ci/woodpecker/pr/pre-commit Pipeline was successful

distinct_resources sends /events to openvoxdb's legacy compiler, which has no
function or group_by, so every backend failed and the client saw a generic 502
instead of the reason.

- Refuse an aggregate carrying a truthy distinct_resources with 400, before any fan-out
- Read the param the way openvoxdb does, so any capitalisation of "true" counts
- Leave non-aggregate distinct_resources queries and every other route alone
- Document the refusal
This commit is contained in:
2026-09-07 18:23:48 +10:00
parent c886617d72
commit 8baa511ef9
5 changed files with 193 additions and 1 deletions
+21
View File
@@ -2,7 +2,9 @@ package main
import (
"encoding/json"
"errors"
"fmt"
"net/url"
"sort"
"strconv"
"strings"
@@ -167,6 +169,25 @@ func groupByClash(name string) error {
return fmt.Errorf("group_by names the aggregate column %q: pdbmux folds that column across backends, so it cannot also be a grouping key", name)
}
const distinctResourcesParam = "distinct_resources"
// errDistinctResourcesAggregate refuses an aggregate asking for the
// distinct-resources form of /events. openvoxdb answers that form from its
// legacy events compiler, whose operator map carries neither function nor
// group_by (src/puppetlabs/puppetdb/query_eng.clj:189-190 and
// src/puppetlabs/puppetdb/query/events.clj:183-187), so every backend fails and
// a fan-out could only report a client mistake as an outage.
var errDistinctResourcesAggregate = errors.New(
"distinct_resources cannot be combined with an extract function column: openvoxdb answers a distinct_resources /events query from its legacy compiler, which supports neither function nor group_by")
// distinctResources reports whether a request asks for the distinct-resources
// form. openvoxdb coerces the param with Boolean/parseBoolean
// (src/puppetlabs/puppetdb/http/query.clj:245-250, applied at query.clj:296),
// so any capitalisation of "true" turns it on and everything else reads false.
func distinctResources(params url.Values) bool {
return strings.EqualFold(params.Get(distinctResourcesParam), "true")
}
// rewriteAvg replaces the client's avg column with the sum and count of the same
// expression, so the true weighted average can be computed from the shards:
// Postgres avg(x) is sum(x)/count(x), and both of those do combine.