Skip to content

External API access to support bundle filters - #11190

Open
smklein wants to merge 5 commits into
support-bundle-time-range-v2from
support-bundle-data-selection-api
Open

External API access to support bundle filters#11190
smklein wants to merge 5 commits into
support-bundle-time-range-v2from
support-bundle-data-selection-api

Conversation

@smklein

@smklein smklein commented Aug 28, 2026

Copy link
Copy Markdown
Collaborator

Stacked on #11113.

BundleDataSelection describes what a support bundle collects: which
categories, which sleds to query, and the bundle-wide time window that bounds
zone logs and ereports. omdb support-bundle collect could already drive that
collection, but Nexus hardcoded BundleDataSelection::all() at bundle creation,
so none of it was reachable over the API.

This adds external API version SUPPORT_BUNDLE_DATA_SELECTION, which
gives SupportBundleCreate an optional data_selection and returns the
stored selection from the view endpoint as a new SupportBundleView.
Omitting data_selection preserves the previous behavior: collect
everything within the last 7 days.

POST /v1/system/support-bundles
{
  "user_comment": "disk errors on BRM42",
  "data_selection": {
    "data": {
      "type": "explicit",
      "reconfigurator": true,
      "host_info": { "sleds": { "type": "specific", "sleds": ["..."] } },
      "ereports": { "only_classes": ["hw.pwr.*"] }
    },
    "start_time": "2026-08-20T00:00:00Z"
  }
}

A couple things worth calling out:

"Collect everything" is an explicit variant, so an empty category selection
unambiguously means "collect none of these" instead of being a rejected
special case. Sled selection then mirrors the internal SledSelection enum
one to one.

omdb nexus support-bundles create grows the --include, --since, and
--until flags that support-bundle collect already had. The
age-to-timestamp handling behind --since and --until is now shared
between the two commands.

Test coverage

The first commit adds unit tests for the conversions between the API type
and BundleDataSelection, including a proptest asserting that every
stored selection survives a round trip through the API type, and cases
pinning what all and an empty explicit selection map to.

The second commit covers the API end to end. A bundle created without a
data selection collects every category and carries the seven-day start
bound Nexus stamps, matching the behavior before the selection was
exposed; asking for everything explicitly is equivalent. An explicit
selection round-trips: a category subset, specific sleds, ereport serial
and class filters, and an explicit window all come back from the view as
they were sent, and an explicit selection naming nothing creates a bundle
that collects nothing. Creation rejects an inverted window and a sled
that does not exist.

One case is about older bundles: viewing a bundle whose time range row is
absent reports no bounds rather than failing. Bundles collected before
time ranges existed have no such row, because #11113's migration only
creates rows for bundles still awaiting collection. The category rows are
not affected, since those were backfilled when their tables landed.

Nexus hardcoded BundleDataSelection::all() at bundle creation, so
everything the selection can express (categories, which sleds host info
covers, ereport serial and class filters, and the bundle-wide time
window) was unreachable over the API.

Add external API version SUPPORT_BUNDLE_DATA_SELECTION, which gives
SupportBundleCreate an optional data_selection, and returns the stored
selection from the view endpoint as a new SupportBundleView. Omitting
data_selection preserves the previous behavior: collect everything, with
the default seven-day lookback stamped at persistence.

Each category's settings live inside that category, so settings for a
category that is not being collected cannot be expressed. That leaves an
inverted time range as the only request validation, plus a lookup that
rejects a selection naming a sled that does not exist.

omdb's `nexus support-bundles create` grows the --include, --since, and
--until flags that `support-bundle collect` already had; the age-to-
timestamp handling behind --since and --until is now shared between them.
Covers what creation accepts and what the view reports back:

- A bundle created without a data selection collects every category and
  carries the seven-day start bound Nexus stamps, matching the behavior
  before the selection was exposed. Asking for everything explicitly is
  equivalent.
- An explicit selection round-trips: a category subset, specific sleds,
  ereport serial and class filters, and an explicit window all come back
  from the view as they were sent. An explicit selection naming nothing
  creates a bundle that collects nothing.
- Creation rejects an inverted window and a sled that does not exist.
- Viewing a bundle whose time range row is absent reports no bounds
  rather than failing. Bundles collected before time ranges existed have
  no such row, because the migration only creates rows for bundles still
  awaiting collection.

Timestamps in these tests come from now_db_precision(): CockroachDB
stores TIMESTAMPTZ at microsecond precision, so a value with nanoseconds
does not come back as it was sent.
@smklein smklein changed the title Let the external API choose what a support bundle collects External API access to support bundle filters Aug 28, 2026
Constructing SupportBundleData::Explicit and then destructuring it back
apart to fill it in needed an unreachable arm to satisfy the compiler.
Accumulate into locals and build the variant once instead.

The match over BundleDataCategory stays exhaustive, so adding a category
still fails to compile here until it is handled.
serde serializes a unit variant of an internally tagged enum the same way
it serializes an empty struct variant, and schemars generates the same
object schema for both: regenerating the OpenAPI document after the
change produces a byte-identical file. The braces were carried over from
DiskBackend::Local {} without checking whether they were load-bearing.
Validating a selection looks up each sled it names, and the lookup
failure for a sled the caller cannot see was being mapped to a 400. An
unprivileged caller who supplied any sleds therefore got "sled ... does
not exist" instead of the 403 the same request earns without a selection.

Authorize Modify on the fleet at the top of the create path, before the
selection is converted or validated. The datastore still authorizes on
its own, so it remains safe to call directly.

Test coverage: an unprivileged create is now rejected with 403 whether it
names no sleds, an existing sled, or one that does not exist. The
existing unauthorized-endpoint coverage did not catch this, because it
posts a body with no data selection at all.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant