Conversation
…l.snapshot.query Naming a user property in extra.properties returned nothing and reported no error, so a caller could not tell an unset property from one this endpoint would never hand over. The names go into query_args['properties'], reach __build_snapshot_cache, and are dropped by its except KeyError: continue, because a user property name is not a ZFSProperty member. Split the names containing a colon out of extra.properties, ask for them with get_user_properties, and merge the ones the caller named into the properties map the entry already carries. No API model change: PoolSnapshotEntry.properties is keyed by an open str. ZFS reports no source for a user property, so the merged entry says LOCAL, the same compromise normalize_user_properties() makes for pool.dataset.query. pool.dataset.query already returns dataset user properties and pool.snapshot.create already accepts them by name; this closes the query side.
|
The asymmetry is due to differences in cost between snapshot retrieval and dataset retrieval N is much higher here potentially by several orders of magnitude. I don't see a reason to add this honestly unless you have some part of our UI that is broken by it. |
|
You're right that N is much larger for snapshots, and that's the correct reason for the asymmetry. But the patch is opt-in, and I measured the cost rather than assuming it, because I'd made the same guess you did.
On 26.0.0-BETA.3, 5000 snapshots under one dataset, each carrying 2 user properties, median of 5 runs: Two things there surprised me. Fetching user properties is cheaper than asking for a single native property (1039 vs 1232 ms), and once any property is already being fetched, adding user properties costs about 2% — 1255 vs 1232 ms across 5000 snapshots. The other half is that this path already ships:
On your actual question: no, I don't have a broken UI. I should have led with that. The driver was that the failure is silent — If the cost is still not worth it to you, I'd rather have the smaller change: reject a name that can't be returned, or document it, so the drop stops being silent. Happy to redo the PR that way, or to close it if you'd prefer to leave the endpoint as it is. Either is fine by me. |
NAS-143371
pool.snapshot.querycannot return a snapshot's user properties. Naming one inextra.propertiesreturns nothing and reports no error, so a caller cannot tell "that property is not set" from "this endpoint will never give it to you".pool.dataset.queryreturns dataset user properties.pool.snapshot.createaccepts them by name. The query side of the snapshot API was the gap.Cause
query()puts everything fromextra.propertiesintoquery_args['properties']. That reaches__build_snapshot_cache, which resolves each name withZFSProperty[i.upper()]inside atrywhoseexcept KeyErroriscontinue. A user property name is not an enum member, so it is dropped there, silently.extra.retentionis not a way round it — it setsget_user_propertiesinternally, butzettarepl.annotate_snapshotspops the whole dict and keeps one removal-date key.Fix
A ZFS user property is exactly a name containing a colon, the same rule
check_user_property_namesuses inplugins/zfs/create_rules.py.query()splits those out, asks for them withget_user_properties, and_transform_snapshot_entrymerges the ones the caller named into thepropertiesmap the entry already carries. No API model change —PoolSnapshotEntry.propertiesisdict[str, PoolSnapshotEntryPropertyFields], so the keys are open.ZFS reports no source for a user property, so the merged entry says
LOCAL. That is the same compromisenormalize_user_properties()already makes forpool.dataset.query, and its docstring says so. Anything else would make the two endpoints disagree.Verified on 26.0.0-BETA.2
source LOCALand the right valuecreation, both come back;creationstillsource NONEextra.retentionandextra.holdsin the same call still workThree tests added to
tests/api2/test_snapshot_query.py.One thing deliberately not patched
The same silent drop also swallows real ZFS properties that are not valid on a snapshot —
__build_snapshot_cacheresolves the name then appliesif prop in valid_propswith noelse. Soextra.properties: ["mountpoint", "quota", "creation"]returns onlycreation, and about fifty names behave that way.I left it alone: it is a separate decision, and unlike the user-property case there is no correct value to return, so the answer is either an error or nothing. The same
except KeyError: continueshape is also inpool.dataset.query'sextra.propertiesandextra.snapshots_properties, so whatever is decided should probably cover all of them. Happy to open a second ticket.Note on shape
I first wrote this as a validation error rejecting any name that is not a ZFS property. Review talked me out of it. Rejecting would be a breaking change to a method with no internal callers, so the whole cost lands on external clients, with no API version to pin against because
extrais untyped; it would also take down the response entirely for a caller who asked forholdsandretentionin the same call and previously got both.Applies to
stable/26unchanged.stable/goldeyehas a different query implementation and would need its own patch.