mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-10-29 16:30:44 +01:00
8f6db360da7b338ccf3651134edf47fe6ec1b1c2
15863
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
8f6db360da |
admin: warn on dashboard when cluster nodes run mixed versions (#12196)
* admin: warn on dashboard when cluster nodes run mixed versions
A partially-finished upgrade leaves servers on different releases, and
nothing surfaces that drift today. The dashboard now compares the
binary version reported by every node:
- volume servers are asked directly via VolumeServerStatus (the
heartbeat topology carries no version), fanned out with bounded
concurrency and a 60s cache since versions only change on restart
- filers, S3 servers, and message brokers already report their version
over KeepConnected; ListClusterNodes returns it, the dashboard just
never read the field
- masters are excluded: raft membership does not carry versions
When every node matches, the Cluster panel shows the shared version.
When they differ, a warning banner calls out the mixed cluster and the
panel lists each node type with its count grouped by version, e.g.
"31x 4.39, 24x 4.44". Version columns on the volume server, filer, and
S3 tables (and the dedicated volume servers page) identify which nodes
are stale. Versions are compared without the volume-size-limit prefix
("8000GB 4.44 abc123" -> "4.44 abc123") so capacity config does not
masquerade as version drift; nodes reporting nothing count as
"unknown".
* admin: run the volume version queries concurrently, not under the map lock
The per-server VolumeServerStatus call ran while holding the versions
mutex, so the semaphore-bounded fan-out was fully serialized: every
server's RPC waited for the previous one, up to the 3s timeout each.
* admin: export the volume server table's real columns
The new Version cell shifted every later column, so the CSV read the
version as Rack and dropped Max Volumes, EC Shards and Usage a slot
early. Map the cells the table actually renders and add Version to the
export.
* admin: warn on mixed versions only when nodes report versions
An empty or master-only cluster produced a non-uniform summary with an
empty node list, which rendered the "Mixed cluster versions detected"
banner and warning icon over nothing. Mixed() requires reported nodes,
and the version card shows a dash when none answered.
|
||
|
|
e9c280e095 |
iceberg maintenance: bound decimal, time, uuid, fixed and rescaled timestamp columns on compacted files (#12190)
* iceberg maintenance: bound decimal, time, uuid, fixed and rescaled timestamp columns on compacted files The column statistics written for a compacted data file give lower and upper bounds only for booleans, integers, floats, dates, strings, binaries and timestamps already stored in the Iceberg type's unit. Every other column is left unbounded, so a reader can never skip a compacted file on it: a DECIMAL(18,2) column written by DuckDB (stored as INT64) loses its bounds after compaction, as does a millisecond timestamp, a TIME or a UUID. boundLiteral now also converts: - DECIMAL stored as INT32 or INT64 at the column's scale. A decimal stored as a byte array still gets no bound: Parquet compares those statistics as bytes, which does not order negative two's-complement values; - TIME, TIMESTAMP, TIMESTAMPTZ and TIMESTAMP_NS from any Parquet time unit, rescaled to the Iceberg unit; a value rescaled to a coarser unit is rounded down for a lower bound and up for an upper bound so it stays a bound, and a value that would overflow gets none; - UUID and FIXED[n] from a FIXED_LEN_BYTE_ARRAY of the right length; - a DOUBLE column read from a FLOAT file. arrow-go becomes a direct dependency (decimal128, already in the module graph through iceberg-go). * iceberg maintenance: bound TIME(MILLIS) columns stored as INT32 * iceberg maintenance: bound timestamptz_ns columns too --------- Co-authored-by: user.email <chrislusf@users.noreply.github.com> |
||
|
|
edf2b0f5ee |
iceberg maintenance: an unreadable parquet file fails the job, not the process (#12188)
* iceberg maintenance: an unreadable parquet file fails the job, not the process parquet.NewReader panics on any file it cannot open (wrong magic, a truncated footer, zero bytes), and parquet-go's row decoder panics on a corrupt page under a valid footer. Maintenance runs in the worker's goroutines with no recover, so one bad delete or data file in any table ended the whole process. - Every parquet open in compaction goes through newParquetReader, which opens with parquet.OpenFile and returns the error. - A panic while decoding a delete file or a data file's rows is recovered into an error naming the file (recoverRead/guardRead). - As a backstop, detection recovers per table and Execute per operation: the table is skipped or the operation fails, and the worker keeps running. A failed delete file still fails the table's compaction (no commit), as a read error did before; a failed data file still skips only its bin. * iceberg maintenance: test a corrupt page inside a merge fails it naming the file * iceberg maintenance: assert a skipped bin's files stay in the snapshot --------- Co-authored-by: user.email <chrislusf@users.noreply.github.com> |
||
|
|
55f2234026 |
iceberg maintenance: apply equality deletes only to older data files in their partition (#12187)
* iceberg maintenance: apply equality deletes only to older data files in their partition
Compaction applied every equality-delete key to every data file of every
bin. The Iceberg spec ("Scan Planning") applies an equality delete file only
to data files with a strictly lower data sequence number, and only to data
files in the delete file's own partition unless its spec is unpartitioned.
An upsert writer (Flink, the Iceberg Kafka Connect sink, CDC tools) commits
an update as the row's new version plus an equality delete of its key in the
same commit, so the compaction deleted both the old and the new version of
every updated row.
- equalityDeleteGroup is keyed by equality field ids and scope (unpartitioned,
or spec id plus partition) and keeps, per key, the highest data sequence
number of the delete files carrying it. A delete file without a data
sequence number is an error rather than a guess.
- Partition values are compared typed through the spec's partition type, so
one partition decoded in different Go shapes (a uuid as uuid.UUID or
[16]byte, a long as int32 or int64, a null present or missing) is one
partition. A spec whose fields are all void is unpartitioned. A delete whose
scope cannot be decided (unknown spec, no partition values under a
partitioned spec, a value that does not convert) stops the compaction; a bin
whose own partition cannot be typed while a delete of its spec exists is
not compacted.
- Each bin is merged with only the groups that apply to its partition, and a
row is deleted only by a key whose sequence number is higher than its data
file's.
- A where filter no longer drops unpartitioned delete files (the clause's
field is not in their spec), which let deleted rows return in a filtered
compaction.
Tests: the table fixture now commits its delete files after the data (data
sequence number 1, deletes 2), as writers do; hand-built entries in the unit
tests get sequence numbers. New eqdelete_sequence_test.go covers rows written
with and after their delete, partition scope, unpartitioned and all-void
specs, typed partition matching, missing sequence numbers and partition
values, where filters, and group-key collisions.
* iceberg maintenance: split the undecidable-scope branch in deleteInPredicateScope
---------
Co-authored-by: user.email <chrislusf@users.noreply.github.com>
|
||
|
|
ec43f4ca00 |
s3: accept bare composite checksum on CompleteMultipartUpload (#12194)
* s3: accept bare composite checksum on CompleteMultipartUpload Clients such as minio-go send the composite object checksum without the "-<parts>" suffix. Compare against the base64 part of the computed composite value as well as the full form. * s3: test object checksum matching with and without part suffix |
||
|
|
4ed9595a58 |
admin: explain detection cooldowns and skipped scans (#12185)
* plugin: describe detection cooldowns and report skipped scans * admin: expose cooldown eligibility and preserve skipped detection outcomes * admin: show cooldown eligibility alongside maintenance lock waits * plugin: retain cooldown activity for older admin servers * admin: keep manual detection skips out of scheduler run state * plugin: refresh descriptors on reconnect without downgrading saved schemas * plugin: allow valid schema refreshes to repair corrupt descriptors * admin: store eligible_after activity detail as RFC3339 * worker: report admin_script interval skips as skipped detection * worker: report admin_script interval skips after the last completed job * admin: show the selected job's cooldown while scheduling is disabled * admin: report an unsigned interval skip instead of a completed scan * worker: populate new DetectionComplete fields in Rust senders Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> --------- Co-authored-by: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> |
||
|
|
cb993ab83b |
filer.redis3: accept username and keyPrefix options (#12192)
* filer.redis3: prefix every key via getKey, honoring keyPrefix Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> * filer.redis3: read username and keyPrefix options into client config Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> * filer.redis3: reject a username configured without a password go-redis only sends AUTH when a password is set, so a configured username with an empty password silently connects as the default user instead of the intended ACL user. Fail loudly at initialize instead. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> * filer.redis3: cover keyPrefix scoping in the store tests Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> * filer.redis3: derive the test prefix and kv key from the per-run directory --------- Co-authored-by: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> |
||
|
|
e7c1713457 |
admin: show maintenance lock blockers in scheduler UI (#12184)
* admin: expose maintenance lock waiters and current owner * admin: display maintenance lock waits across scheduler pages * admin: preserve the active lock message while acquisitions wait * admin: synchronize lock fairness assertions with waiter registration * admin: display lock wait start times alongside elapsed duration |
||
|
|
6cd14f06c9 |
filer.remote.sync: upload a holed entry zero-filled instead of skipping it (#12178)
* filer.remote.sync: skip an event the filer has written past, not only rewritten
isSuperseded only looked for chunks the event named that the filer no
longer holds. A file copied in through a mount logs a create while the
copy is still running: the mount flushes metadata with chunk uploads in
flight, so that chunk list can have holes. ChunkStreamReader stops at
the first hole, the multipart upload sends a short part ("ContentLength=
8388608 with Body length 4194304"), and since every chunk the event
names is still live the event was never superseded. The daemon retried
it forever and the offset never moved past it.
Treat chunks the filer has added since the event the same way. The
event that added them follows in the log and uploads the finished file.
* filer.remote.sync: skip a holed snapshot even while the filer still holds it
A mid-write snapshot the writer never finished is neither deleted nor
written past: the filer holds it unchanged, so the superseded check never
fires and every retry reads short at the same gap, pinning the sync offset
forever. The upload can never succeed for that entry — skip it like a
superseded one. A finishing write logs its own event and uploads the full
content; a dead write has nothing readable to lose.
The check also covers a chunkless entry that is not a dropped local copy
(e.g. a truncated remote-only entry): its reader yields zero bytes against
a nonzero size, which can silently clobber the remote object with an empty
body where the backend does not verify the length.
* filer.remote.sync: upload a holed entry zero-filled instead of skipping it
Skipping the event left a completed sparse file absent or stale on the
remote, and the coverage check on the raw chunk list missed both gaps a
manifest's bounding extent hides and a FileSize smaller than the chunk
extent. ChunkReadAt already resolves manifests the way the upload reader
does and zero-fills every uncovered range up to the effective size, so
route the upload through it: a sparse file, a mid-write snapshot, and a
chunkless entry all send the bytes a read of the entry returns, and a
hole can neither wedge the event nor publish a truncated object.
Generated with [Devin](https://devin.ai)
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
* filer.remote.sync: skip a superseded entry on reader-construction error too
---------
Co-authored-by: Alex Russell <alex@sengine.cloud>
Co-authored-by: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
|
||
|
|
07636335cf | docs: regenerate star history chart | ||
|
|
6225e0a705 |
s3: return x-amz-copy-source-version-id from CopyObject and UploadPartCopy (#12181)
* s3: return x-amz-copy-source-version-id from CopyObject and UploadPartCopy * s3: cover x-amz-copy-source-version-id on latest copies and part copies The explicit-version case was already covered; add a .versions pointer fixture so the unversioned request resolves the latest version and asserts the header on CopyObject, plus the same assertion on UploadPartCopy. * s3: carry the resolved id on a version file that lacks it A .versions child written before the version-id attribute resolves to an empty source version even though the pointer or the rescan already knows it. Stamp the id on the returned entry so copy and get readers report it; the version copy tests now drop the attribute to cover that shape. * s3: report the null version in x-amz-copy-source-version-id A regular-path source in a versioning-configured bucket is the null version; echo it like every other version id, matching the GET path and AWS. Unversioned sources still report no header. * s3: report the version a self-copy retry actually read --------- Co-authored-by: user.email <chrislusf@users.noreply.github.com> |
||
|
|
48782e2b30 |
s3: allow PutBucketAcl when auth is off (#12180)
* s3: allow PutBucketAcl when auth is off * s3: resolve the PutBucketAcl owner against the recorded owner The unauthenticated path now resolves the account as anonymous, but XML ACLs were still checked against it while GetBucketAcl reported an empty owner for ownerless buckets, so a read-modify-write ACL update failed with AccessDenied. GetBucketAcl and the XML check now both use the recorded owner when the bucket has one and the resolved account otherwise. The canned-ACL grantee moves to the same owner, and a request carrying no account no longer erases a stored owner. * s3: cover PutBucketAcl without auth for XML bodies and stored owners The XML case posts the anonymous owner GetBucketAcl reports for an ownerless bucket; the canned case asserts a recorded owner survives an unauthenticated update and receives the full-control grant. Ambient AWS credentials are cleared so the memory store never turns auth on. * s3: keep the recorded owner through a PutBucketAcl update The grantee and the stored owner were resolved differently: grants named the recorded owner while config.Owner still took the caller's account, so an admin updating another account's bucket rewrote the owner and broke an XML retry naming the original one. An ownerless bucket adopts the ACL's owner; an owned bucket keeps it. * s3: fall back to the admin owner for an ownerless bucket in ACL handlers An unauthenticated PutBucketAcl resolved the owner as anonymous and stored it on the bucket, but the registry reports an ownerless bucket as admin-owned: BucketOwnerEnforced uploads then stamped anonymous instead of admin, and ownership checks after enabling auth compared against the wrong owner. Both ACL handlers now resolve an anonymous fallback to the admin account, so GetBucketAcl reports the same effective owner the registry does and the stored owner matches it. * s3: resolve the admin owner for bucket grants when the caller is anonymous or untrusted Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> * s3: skip recording anonymous identity or account as bucket owner Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> * s3: report stored bucket ACL grants verbatim Explicitly saved anonymous full-control grants must survive a GET-to-PUT round trip unchanged, so the read path no longer rewrites them to the reported owner. Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> * s3: expect the stored anonymous grant verbatim in GetBucketAcl Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> --------- Co-authored-by: user.email <chrislusf@users.noreply.github.com> Co-authored-by: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> |
||
|
|
fffdb7cc75 |
filer.remote.sync: skip an event the filer has written past, not only rewritten (#12176)
isSuperseded only looked for chunks the event named that the filer no
longer holds. A file copied in through a mount logs a create while the
copy is still running: the mount flushes metadata with chunk uploads in
flight, so that chunk list can have holes. ChunkStreamReader stops at
the first hole, the multipart upload sends a short part ("ContentLength=
8388608 with Body length 4194304"), and since every chunk the event
names is still live the event was never superseded. The daemon retried
it forever and the offset never moved past it.
Treat chunks the filer has added since the event the same way. The
event that added them follows in the log and uploads the finished file.
|
||
|
|
74f196ceab |
s3: route regular copy source reads to the write owner (#12173)
* s3: read regular copy sources from their write owner * filer: report mutation sentinel codes in ObjectTransaction responses A failed mutation only surfaced resp.Error text, so routed writers could not distinguish an existing-directory conflict from a generic failure and could not apply the prefix-object retry the plain CreateEntry path has. * s3api: retry a routed mkfile as a prefix object over a directory mkFile retries ErrExistingIsDirectory by marking the entry a prefix object; the routed path returned the conflict instead, so a routed write to a key that is also a prefix failed where the lock path succeeds. The filer now reports the code on the transaction response, making the retry recognizable. * s3api: commit copy destinations on the key's write owner An owner-routed source read still left the destination on the gateway's default filer, so a second copy resolved the first copy's destination on the owner, found nothing, and returned NoSuchKey. Write unversioned and suspended destinations through routedMkFile, and write the version file on the owner the finalize recomputes on. * s3api: cache remote objects on the filer that served the entry An owner-routed lookup can return a remote-only entry that has not replicated to the gateway's default filer; the cache RPC then cannot find it and the copy or read degrades to a 503. Ask the entry's write owner to cache it first, falling back to the default filer. * s3: read versioned object state from the key's write owner CopyObject destinations now commit version files and the .versions pointer on the owner's object-scope ring key, so versioned reads through the default filer could miss them until replication. Generalize the routed read to dir/name lookups and paginated lists scoped to the object's owner, and point the versioned read paths at it: getSpecificObjectVersion, doGetLatestObjectVersion, the .versions quick-checks in Get/Head/attributes, the self-heal rescans, tagging, delete conditionals, directory-marker history checks, rename destination, multipart completion checks, and the version-id format probe. Reads fall back to the default filer when no ring owner is resolvable, preserving single-filer deployments. * test(s3api): fake RECOMPUTE_LATEST/DELETE and cover versioned copy reads The ambiguous-PUT fake filer now creates parent directories on PUT (mirroring InsertEntry), applies DELETE mutations, and re-derives the .versions pointer for RECOMPUTE_LATEST the way applyRecomputeLatest does, so owner-vs-replica tests can exercise versioned writes. TestVersionedCopyDestinationReadsOnOwner copies into a versioned bucket whose writes commit on the owner while the gateway's default filer is a replica with no replication, then asserts reads by versionId, latest-version resolution, and a copy chained off the new version all resolve on the owner. * test(s3api): gofmt the versioned-copy coverage * s3: only swap in a cache fill of the resolved remote generation A cache response can come back from a filer holding an older generation of the key. Replacing the authorized source or swapping its chunks under a size and range computed for the resolved entry serves the wrong object or truncates the response. Match RemoteEntry identity and size before using a cached entry, in both the copy and streaming paths. * filer: keep directory attributes when an object lands as prefix object The object-onto-directory retry replaced the existing directory's mode, owner, and extended attributes with the object's. Preserve the directory identity; merge directory-held extended keys only when the old entry never held an object, so a prior generation's object metadata is not carried forward. * s3: keep marker lock checks on the filer that replaces the marker The object-lock precondition read the key's write owner while mkdir still replaces the marker on the default filer, so a locked marker not yet replicated to the owner could be overwritten. Read the entry where the replacement lands, in both PutObjectHandler and zip extraction. Also hash a trailing-slash marker key to the same route key as the object it names, so marker and object share one owner and one write lock. * s3: route tag writes to the owner tag reads resolve GetObjectTagging reads the key's write owner, but PutObjectTagging and DeleteObjectTagging still wrote through the default filer, so a successful change returned the owner's stale tags until replication. setTags, rmTags, and the versioned tag updates now write on the owner first, falling back to the default filer when no owner resolves. * s3: keep rename lookups on the filer that performs the move AtomicRenameEntry and the retry token it carries both execute on the default filer, but the source and destination reads went to the write owner — a completed rename's token was invisible to its own retry, and a source that only exists on the default filer looked absent. Read both ends on the default filer. * s3: send the .versions pointer repair with the object's route key The heal reads the .versions entry on the object's owner, but the conditional UpdateEntry is routed by the .versions path's own ring key, which can hash to a different filer — leaving the pointer the routed reads consult unchanged. Ship the CAS repair as a PATCH_EXTENDED ObjectTransaction on the object's route key instead. * filer: preserve only directory identity on a prefix-object write Merging the old directory's extended attributes could carry a marker's encryption, version, or etag fields onto the new plaintext object, since a directory's own keys cannot be told apart from the object it held. Keep only mode, uid, gid, user name and group names. * s3: also require matching size when a cache fill replaces the source The remote-generation check covers identity; size equality is the cheap guard that keeps the streamed or copied range consistent with what was resolved. * s3: lock the object path for a routed .versions pointer repair conditionalUpdateEntryRouted held the lock on the .versions path while version writes and deletes serialize on the object path, so a repair could pass IF_ENTRY_EQUAL and then apply over a pointer a writer had advanced in between. Lock the object path like the writers and keep the condition on the .versions entry via condition_key. * s3: do not replay an owner-side tag mutation on another filer withWriteOwnerFilerClient re-ran fn on the default filer after any owner error, so an authoritative not-found could update a stale replica entry and still report success, and a post-apply transport error could apply the mutation twice. The failover walk now ends on any error fn returns; only a channel that never came up moves to another filer. * filer: keep a directory's xattrs when a prefix object replaces it The prefix-object branch preserves the directory's identity but dropped its Extended map wholesale; FUSE xattrs live there under the xattr- prefix and belong to the directory, not to the object the marker held. * s3: delete null and specific versions on the filer that serves them On the default filer an absent entry deletes as success, so a version that only reached the write owner survived a reported success while owner-routed reads kept serving it. The suspended-versioning and lifecycle deletes now go through routedDeleteNullVersion and routedDeleteSpecificVersion when an owner resolves; an unsettled attempt keeps the lock path. * s3: route a lifecycle null-version delete to the object entry The "null" version is the regular object entry, not a .versions file, so it goes through routedDeleteNullVersion; only a real version id takes the .versions-file delete. * s3: repoint .versions only while it still names the deleted version routedDeleteSpecificVersion recomputed the latest pointer on every version delete, so removing a noncurrent version re-derived the pointer to the newest remaining file — resurrecting an older version over a cleared pointer (a suspended bucket's null write keeps it clear while the null object stays current) or over a pointer that moved on. The pointer is now read on the owner and repointed under an IF_ENTRY_EQUAL guard only while it still names the deleted version; a contested pointer is left for the self-heal path rather than overwritten by a stale scan. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> * s3: let lifecycle expiry keep its metadata-only deletes on routed paths The routed lifecycle deletes hardcoded IsDeleteData, so expiring an entry with a volume TTL queued per-chunk deletions instead of leaving the data for the volume to reclaim — extra work on the owner for deletes recordMetadataOnlyIf still counts as metadata-only. Pass the caller's cleanup choice through routedDeleteNullVersion and routedDeleteSpecificVersion; the interactive DELETE path keeps deleting data. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> * filer: let RECOMPUTE_LATEST skip a pointer that moved on A version delete repoints .versions only while the pointer still names the deleted version, but the gateway cannot carry the pointer's current state into the transaction's condition — the condition slot is already spent on the version's WORM guards. only_if_current_name moves the check under the transaction lock: the filer compares the pointer's name_to_key value before scanning and skips the recompute when it moved (or was cleared by a suspended bucket's null write), so a noncurrent delete cannot resurrect an older version. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> * s3: repoint inside the version's locked delete repointLatestBeforeVersionDelete ran in its own transaction ahead of the delete, so a version delete denied by retention or legal hold still moved the latest pointer — GET and HEAD went on serving the older version it rolled to. The repoint and the delete now ride one transaction behind the version's WORM guards, atomically gated by the new only_if_current_name recompute guard instead of a stale-read IF_ENTRY_EQUAL. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> --------- Co-authored-by: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> |
||
|
|
8daa1b6ddd |
vector: Lease::forget hands the tag name off without releasing (#12174)
* vector: Lease::forget hands the tag name off without releasing A distributed planner pins the planned version and forwards the tag name to the executor — the guard must not release on drop. * vector: forget untracks the lease so an abandoned handoff expires The tag left LIVE_TAGS but the drop-time release was disabled — an abandoned handoff survived forever locally. Untrack it so the lease TTL still reaps it. |
||
|
|
511727cf3a |
vector: DeleteIndex format guard, sticky discovery, advertise normalize (#12171)
* vector: DeleteIndex format guard, sticky discovery, advertise normalize - handleDeleteIndex verifies the table carries the Lance format before DeleteTable — an Iceberg table under the same namespace is not an index and must not be removed by it. - discovery keeps the current worker while it is still registered — membership-order churn can no longer cancel in-flight calls, and the resolver closes each master conn inside a 5s deadline. - advertised_addr strips scheme and .grpcPort so the UDP probe reaches every supported master form. * vector: empty-follower retry, format check without read grant - discoverVectorWorkers keeps looking past an empty master reply — followers list nothing when workers registered with the leader. - the DeleteIndex format check uses the internal catalog read, so DeleteTable-only callers don't gain a GetTable requirement, and the format must be Lance exactly — format-less tables no longer pass. |
||
|
|
ae95b2c777 |
ec: rebuild a needle's suspect shard ranges when it fails to parse (#12170)
* ec: let shard-interval recovery exclude a set of suspect shards gatherEcShardIntervals and recoverOneRemoteEcShardInterval skipped only the shard being rebuilt. A needle-level rebuild wants every suspect shard kept out of the gather so a bad copy cannot poison the reconstruction it is being compared against. * ec: rebuild a needle's suspect shard ranges when it fails to parse A bit-rotted data shard serves a clean-looking but wrong interval, so the missing/unreadable-shard fallback never engages and the needle CRC check in ReadEcShardNeedle returns the corruption to the caller. Each interval's source shard is a suspect: rebuild the suspect ranges from the surviving shards — all of them at once while parity covers the suspect set, or one at a time when the needle spans more shards than that — and let a successful needle parse arbitrate which copy was corrupt. Shards whose range changed are dropped from the location cache so later reads go straight to reconstruction. * ec: mirror the corrupt-shard needle rebuild in the Rust volume server The snapshot now keeps every interval's shard placement so a parse failure can name its suspects. rebuild_ec_shard_needle rebuilds the suspect ranges through recover_one_remote_ec_shard_interval with the suspect set excluded from the gather, and the winning candidate's differing shards are dropped via forget_shard_location. * ec: recover suspect ranges under the requested needle id A failed ReadBytes re-reads the id out of the possibly corrupt header, so the rebuild passed whatever it decoded to peer recovery requests; carry the requested id through instead, and reject a candidate whose parsed id does not match it. * ec: retry a failed needle rebuild with each gathered input dropped The gather behind a suspect-range rebuild can itself serve a second corrupt shard: Reed-Solomon then reconstructs wrong bytes and the needle parse rejects the only candidate. Gather every surviving shard once, then re-decode with each of the first decode's inputs dropped in turn; the parse arbitrates the honest subset. * ec: mirror the dropped-input rebuild and id check in the Rust volume server Same two guards as the Go side: a candidate that parses under a different needle id is rejected, and a failed candidate whose gather fed it a corrupt input is retried by dropping one gathered shard at a time. The gather is shared through a want-bounded gather_ec_shard_intervals so the retry pays no extra fan-out. * ec: answer deleted when a rebuild's holder reports the needle deleted The rebuild discarded the deleted flag its interval recovery collected, so a needle deleted on its other holders but live in the local index came back as a corruption error. A deleted report is authoritative, so it now short-circuits the hypothesis search and answers ErrorDeleted. * ec: hydrate rebuild candidates into a scratch needle A failed ReadBytes leaves partially parsed fields on the needle, and optional sections do not reset when absent, so a corrupt first parse could leak stale name/mime/ttl into a successful candidate. Parse each candidate into a fresh needle and adopt it only on a matching id. * ec: corrupt parity+1 shards in the exceeds-parity read test The loop skipped the suspect's own shard when it fell inside the parity range, leaving only ParityShards corrupt shards — a case recovery can legitimately fix. Count the additional corruptions so the test always exercises the boundary it names. * ec: mirror the deleted-report short-circuit in the Rust volume server A holder's deleted answer during a rebuild propagates out as EcMiss::Deleted instead of surfacing as InvalidData, matching the Go path's authoritative-delete rule. |
||
|
|
eec2452e30 |
master: replicate the recovery-wait growth unfreeze through raft (#12165)
* master: replicate the recovery-wait growth unfreeze through raft state_loss_recovery_wait lifted the volume-growth freeze only in memory, while the freeze itself was replicated. A restart replayed the frozen flag and growth stayed refused until the wait expired again, on every restart, unless an operator ran volume.growth.resume. The lift now applies GrowthFrozen=false like ResumeVolumeGrowth, from a goroutine since IsGrowthFrozen is a read path that cannot block on raft. The apply runs under reportedTopologyIdsLock so a freeze landing after the lift keeps its command last in the log; the stale unfreeze skips itself when the flag is frozen again. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> * master: cover raft replication of the recovery-wait unfreeze Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> * master: retry and re-arm a growth unfreeze raft failed to record applyGrowthFreeze swallowed the raft error, so a rejected lift during a leader change left the flag clear while the log still held the freeze; nothing retried and a restart re-froze growth anyway. The apply now returns the error, replicateGrowthUnfreeze retries it, and on persistent failure re-arms the flag so the next IsGrowthFrozen attempts the lift again instead of diverging from the log. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> * master: release the topology-id lock before the freeze raft write CheckReportedTopologyId and SetTopologyInitPending held reportedTopologyIdsLock across the blocking raft apply, so a slow apply stalled every later report — including the heartbeat that should trigger the next freeze. The lock now covers only the mismatch decision; the replicate step runs after it is released. * master: bind the recovery unfreeze to the freeze instant it lifts An unfreeze submitted while a newer freeze sat committed but not yet applied — e.g. after this node won an election — could land behind that freeze in the log and erase it. The command now carries the freeze instant cluster-wide (GrowthFrozenAt) and the lift is bound to the instant its submitter observed (GrowthUnfreezeFor): the state machine no-ops a lift while a strictly newer freeze exists, which also lets the unfreeze run without holding the topology-id lock at all. Concurrent freeze writes keep their own mutex so log order cannot regress the instant. * master: a superseded unfreeze retry leaves the resumed flag alone replicateGrowthUnfreeze broke out of its retry loop when a newer\nfreeze superseded the lift, but the break fell through to the\nre-arm: an operator resume in between was silently undone. Return\ninstead — the newer freeze generation owns the flag. * master: replayed freeze entries without an instant stay ageless Freeze commands written before the instant field existed carry zero.\nSubstituting the replay time made each restart stamp the freeze as\nnew, so a committed bound unfreeze always looked older and was\nrejected — the freeze came back on every restart. Keep the zero\ninstant: the recovery window reads as elapsed and bound lifts apply. * master: pack the growth freeze flag and generation into one atomic Separate flag and timestamp atomics left three holes: a resume's\nload-and-clear could lift a generation stamped in between, a bound\nlift compared stamps numerically although masters' clocks need not\nbe monotonic, and an ageless generation had no identity a bound lift\ncould match without colliding with the unconditional encoding.\n\nOne value now carries the state — 0 unfrozen, -1 an ageless freeze,\nelse the freeze instant — so every transition is a single atomic\nswap or compare-and-swap bound to an exact generation. * master: a repeat mismatch report keeps the armed freeze generation Re-stamping while already frozen never reaches the raft log, so the local generation diverges from the replicated one and every bound unfreeze becomes a no-op on replay — the freeze returns after restart or failover. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> * master: snapshots read the freeze flag and generation in one load Save, Snapshot, and PersistMaxVolumeId read IsGrowthFrozen and the generation separately, so a resume landing between them persisted frozen=true with stamp 0 — restoring an ageless freeze a committed bound lift cannot clear. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> --------- Co-authored-by: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> |
||
|
|
b62bbb99fc |
fix(iceberg): serve a registered table's metadata on load and commit (#12159)
* iceberg: serve a registered table's metadata on load and commit RegisterTable saved only the metadata location, and loads and commits never read the file there. They served a placeholder with no schema, no snapshots and a new random table UUID on every load, so a registered table read as empty, an INSERT failed against the empty schema, and every other commit failed its assert-table-uuid requirement. Metadata uploaded through the S3 gateway, as a migrated table's is, is stored in chunks that registration did not read, so it registered an empty table. Read the file following its chunks, reject one that is not Iceberg metadata with a 400, and keep its content and table UUID in the record the way CreateTable does. Records registered before this change hold only the location. Loads and commits now read the file there, and the first commit stores it in the record under the file's table UUID. * iceberg: transactions read a location-only registration's metadata file * iceberg: cover transactions on a location-only registration * iceberg: refresh the default name mapping on transaction commits * iceberg: cover name-mapping refresh in transactions * iceberg: roll a transaction back to a registration's metadata file A transaction that failed after flipping a table registered before the record kept its metadata could not undo the flip. The rollback restored the record's prior metadata, which for such a table is only a location, and UpdateTable keeps the full metadata a flip stored when the restore sends none. Loads went on serving the change the client was told had failed, and a client retrying the transaction could apply it twice. Restore the metadata loads served from the file instead, under the file's table UUID. * iceberg: register oversized metadata by its location Registration sends the metadata file's content to the S3 Tables manager, which refuses a request over its 10MB body limit, so a table whose metadata is larger could no longer be registered at all. Register such a file by its location and table UUID alone; loads read it from that location, as they do for a record that never kept its metadata. Export the limit as s3tables.MaxRequestBodySize so the catalog can check against it. A commit to such a table still sends the whole metadata to UpdateTable and stays over the limit. * iceberg: commit oversized metadata by its location * iceberg: let maintenance follow a location-only table record * admin: read a table's metadata file when the record keeps none * iceberg: bound the metadata file a location-only record reads * iceberg: bound only chunked metadata reads in the worker Inline content is what the catalog itself wrote and the REST load path accepts it at any size, so rejecting it in loadMetadata stranded tables over 64 MiB from every maintenance scan. Cap only the chunked read, where the recorded location can name any object in the bucket. --------- Co-authored-by: chrislusf <chrislusf@users.noreply.github.com> |
||
|
|
a816c6202c |
sftp: fail rm and rmdir of a missing path, and mkdir of an existing one (#12168)
* sftp: fail rm and rmdir of a missing path, and mkdir of an existing one * sftp: create directories exclusively --------- Co-authored-by: user.email <chrislusf@users.noreply.github.com> |
||
|
|
3fd8d0cf09 |
master: let location-constrained assigns wait for pending growth (#12167)
* topology: add AvailableSpaceWithinLocation for location-scoped free slots AvailableSpaceFor answers for the whole topology; a location-constrained assign needs to know whether the requested data center, rack, or data node still has room before deciding growth can satisfy it. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> * master: let location-constrained assigns wait for pending growth An assign carrying a data center or rack constraint failed on the first unsatisfied pick, even when the same request had just queued volume growth for that location — so the first write into a fresh collection errored out instead of picking up the volume growth landed a moment later. Keep the fail-fast path only when growth cannot help: growth disabled, no growth pending, or no free slots left inside the constraint. Otherwise the request takes the same wait/shed path as an unconstrained assign. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> * topology: sum leaf free slots in AvailableSpaceWithinLocation Rolled-up rack and data center counters keep a maintenance-mode server's capacity, and one overcommitted node can cancel a sibling's slots. Sum the matching data nodes directly so the location check agrees with what growth can actually use. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> * master: let constrained growth initiators reach the shed path When a constrained assign's own growth concluded unfulfilled, the fast-fail check saw only that no grow was pending and returned the plain pick error — a code the assign client does not retry, so the first write died instead of re-triggering growth. Exclude the request's own initiated grow from the no-pending-growth condition so it reaches the existing re-pick and ResourceExhausted shed like an unconstrained assign. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> * master: judge targeted grow requests by the location they name A grow request carrying a data center, rack, or data node constraint was discarded whenever the layout's writables elsewhere were healthy, so a location with free slots but no writable could never receive the volume its assign was waiting for. Add ShouldGrowVolumesWithinLocation and use it for constrained requests; the layout-wide check still governs untargeted ones. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> * topology: only positive leaf slots count in AvailableSpaceWithinLocation An over-committed node reports negative free slots; summed plainly it\ncancelled a sibling's room and a constrained assign could fail fast\nwhile the location still had space to grow into. * master: fail constrained assigns fast when growth cannot place A constrained assign with free slots in its location still waited out\ngrowth that could never satisfy the replica placement — a single-rack\ndata center asked for 010 sheds the same doomed grow on every client\nretry. Run the grow-time node search as a dry run and return its\nplacement error instead of a retryable shed. * master: judge constrained growth by layout, not free slots A slot-level feasibility probe races with the grow it judges: a\npending grow consuming the last slot in one rack reports the\nplacement impossible while it mints the request's own volume, and\nthe hard error skips the re-pick that would find it. GrowthBlocker\nnow counts racks and data nodes inside the named location — shape a\ngrow cannot consume — so only genuinely unplaceable layouts fail\nfast. * topology: GrowthBlocker mirrors the grow selector's feasibility The location filter bound every replica tier, so a 010 request naming one rack needed two racks inside it while the selector constrains only the first placement and counts the rest by room. And counting nodes without free slots let a full location queue a grow it could never serve, so the assign shed ResourceExhausted at timeout instead of failing fast. GrowthBlocker now applies the option's location to the primary placement chain only and leaf-sums free slots the way the selector does, so it reports a blocker exactly when the grow can never place. * master: repick once before surfacing a growth blocker An in-flight grow can spend the last free slot minting the request's own volume: the blocker's slot counts predate that registration by a heartbeat, so returning the placement error on the first failed pick could reject a write whose volume lands a moment later. A pending grow now earns one repick grace before the blocker error is returned. --------- Co-authored-by: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> |
||
|
|
18691e7b5b |
server: keep the first write on a fresh weed server from stalling (#12149)
* master: end the post-mint allocation hold on the embedded reporter's clean inventory DeferAllocation armed a warmup-length hold whenever the leader minted a fresh topology id, and on weed server the hold ran the whole window: the embedded volume server's join kept refreshing the quiet evidence clock, so the two-pulse early release still cost the first write 10-15s on every fresh start. weed mini already releases on its single pinned reporter's first inventory, but pinning refuses outside volume servers, which weed server must keep admitting. Add a non-exclusive variant: the topology names one expected reporter whose first clean full inventory lifts the hold, while other reporters stay admitted and still feed prior-state evidence and the topology-id freeze. A single-master weed server points it at its embedded volume server — the one reporter guaranteed to arrive quickly; a veteran's heartbeat carries prior state and keeps the window through the regular checks, so a wiped -dir with surviving external volume servers still trips the freeze on first report. Restarted clusters are untouched: a recovered topology id means no mint and no hold. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> * filer: announce on the startup peer scan for embedded single-master setups A fresh filer whose startup peer scan came back empty re-polled for the full confirm window before registering on the master, so the lock ring had no member and routed traffic such as S3 PUTs spun on "no lock server found" for ~10s on every fresh weed server / weed mini start. For setups that start with exactly one filer the scan is already final: a single-master weed server and weed mini announce as soon as it completes. A late external filer still joins and replays through the normal path — the confirm window only ever caught peers arriving inside it, and the residual risk mirrors the accepted volume-side trade-off: an embedded filer announcing a few seconds earlier can own the lock ring while a surviving external filer's backlog is still on its way. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> * volume: retry a failed heartbeat promptly until first registration A heartbeat stream refused before the leader is known — the common race when a volume server starts while an election is still running — always fell back to sleeping out the full pulse, so a fresh volume's first registration could wait ~5s behind a leader elected a second later. The peer refresh already identifies the leader during the wait, so cap the retry pause at a second once a leader is named; while no leader is known the same short retry applies until the first stream is confirmed, since an unregistered server has nothing else to do. Once the server was registered the pulse backoff is unchanged, so a lost leader still paces reconnects the way it did before. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> * volume: retry a failed heartbeat promptly until first registration (rust) Mirror the Go heartbeat retry: cap the post-refresh wait at a second when a leader is named, and keep the short retry until the first stream is confirmed so a fresh volume registers promptly when it races an election. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> * master: leave admitted reporters a confirm pulse when the embedded report ends the hold The single-master weed server releases its post-mint allocation hold on the embedded volume server's clean report, but outside volume servers stay admitted and one may still carry prior cluster state — an inventory from the embedded server alone cannot prove none is due. Clearing the hold outright let a rebuilt master re-issue a volume id a reconnecting veteran still owned. Collapse the released hold to one heartbeat interval instead: late reporters land their evidence or a topology-id freeze inside the pulse, and a re-mint re-arms that pulse rather than the full warmup. The pinned single-reporter release (weed mini) still drops the hold outright, since no other reporter can be admitted. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> * filer: keep the empty-membership confirm on embedded single-master weed server A single-master weed server still admits external filers, so an empty startup peer scan is not final: after a master restart the existing filers simply have not reconnected yet. Announcing on the scan let the embedded filer take lock-ring ownership and serve writes that a peer's replayed backlog could then overwrite. Only weed mini keeps the skip, where the embedded filer is the only member its dev cluster can have. The confirm window only delays the master listing for a fresh store; the filer serves while it runs. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> * volume: tolerate an expired heartbeat retry deadline (rust) refresh_leader_from_peers can consume the whole pulse budget, leaving retry_at in the past; subtracting a later Instant panics and kills the heartbeat task instead of retrying. Match the Go side, where time.Until of a past deadline just sleeps zero. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> * volume: retry the seed list without a second pulse sleep (rust) The error path already slept its retry delay; an extra full pulse after losing the round delayed the next attempt past what Go's heartbeat() does, which paces retries only inside the per-master attempt. * master: keep a configured allocation hold shorter than the confirm pulse Once the embedded report lands, the collapsed hold replaced the configured deferral with a full pulse even when the operator set a shorter one, including zero (disabled). Cap at the pulse instead. * filer: end the empty-membership confirm early on a fresh-minted embedded master On a fresh single-master weed server the announce gate still held the filer for the full confirm window, so the master's lock ring stayed empty and the first write retried 'no lock server found' for ~7s on top of the collapsed allocation pulse. A fresh TopologyId mint proves the master's persisted state was empty, making the empty peer scan final; skip the rest of the confirm only then, and keep it whenever the master recovers a prior id. * filer: keep a short reconnect window on a fresh-minted embedded master A fresh TopologyId mint proves the master's persisted state was empty, not that no external filer survived its loss — one can still be mid-reconnect on its keepalive cycle when the confirm starts. Ending the empty-scan confirm on the first poll let the fresh filer register and serve routed reads before that peer was found and backfilled, so existing files could read as missing. The fresh-mint path now shortens the confirm to a few keepalive cycles instead of skipping it: a reconnecting filer still lands in time, while a genuinely fresh cluster announces after ~3s rather than ~10s. * volume: skip the heartbeat loop when no seed masters are configured An empty seed list made the outer loop spin with no await between passes and no path to observe the stop flag. A masterless volume server has nothing to register with, so the heartbeat exits after a warning. * volume: skip the heartbeat loop when no master addresses are given (rust) Mirrors the Go heartbeat: an empty seed list spun the outer loop with no await and no shutdown checks. Exit after a warning so library callers get the same behavior as the binary path that validates the list up front. * filer: re-check the fresh-mint hint on each confirm pass The topology id is minted after raft election, which can land after the empty-scan confirm has already started — a one-time check at entry missed the mint and held the full window. Evaluate the hint on every poll and shorten the confirm from the first observed mint, still capped by the normal confirm deadline. * master: mark a fresh topology mint only when the volume bound is zero Recovery restores the max volume id unconditionally but the topology id only when present, so an upgraded or partially rebuilt master can mint a new id while still holding prior volume state. Marking fresh there would let the embedded filer shortcut its peer confirm on a cluster that is not empty; only mark the mint fresh when no bound survived. * filer: drop the fresh-mint shortcut once a peer has been observed --------- Co-authored-by: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> |
||
|
|
9059cc2a07 |
s3: allow PutObjectAcl when auth is off (#12155)
* s3: allow PutObjectAcl when auth is off * s3: ownerless objects claim the admin account when auth is off An object written through the filer carries no owner metadata, so objectOwner fell back to an empty account id and ExtractAcl rejected the canned ACL's canonical grantee. Extend TestPutObjectAclWithoutAuth to cover the ownerless case. * s3: GetObjectAcl reports the admin owner for ownerless objects when auth is off The same empty-owner fallback produced an ACL response with a blank Owner ID and a blank canonical grantee. * s3: ignore the client-supplied account id when auth is off Auth does not strip AmzAccountId on the iam-disabled path, so the ownerless fallback could persist a forged owner. requestAccountId substitutes the admin account, matching the idiom already used in the upload ACL path. --------- Co-authored-by: user.email <chrislusf@users.noreply.github.com> |
||
|
|
b3d0caabe2 |
vector: only follow a leader hint that points elsewhere (#12164)
* vector: only follow a leader hint that points elsewhere The master sends volume_location on every KeepConnected response — the leader always names itself, so the worker exited the stream on the first response and flapped every 3s, leaving ListClusterNodes to see the node intermittently. Only redirect when the hint points at a different master. * vector: leader hints carry host:http.grpc — normalize before comparing A self-hint arrives as host:9333.19333 while --masters gives host:9333, so the raw comparison still redirected and grpc_addr could not parse the dotted port. grpc_addr now prefers the explicit .grpcPort, and the hint comparison normalizes both sides to host:httpPort first. |
||
|
|
c2863d9a4f |
vector: owner-prefixed lease tags stop cross-server reaping (#12158)
* vector: owner-prefixed lease tags; foreign leases get a long backstop
reap_expired exempted only process-local LIVE_TAGS — server B could
reap server A's live query lease at the 1h TTL. Tags now carry a
per-process instance id: own leases use the short TTL, foreign leases
only the 24h backstop, so a live query elsewhere keeps its pin while
dead-process tags still collect eventually.
* vector: pick the lease-tag format by segment count, not numeric-ness
An owner that is all decimal digits parsed as the legacy timestamp, so
its real timestamp was ignored and the lease never expired. The third
segment is what distinguishes {instance}-{ts}-{uuid} from {ts}-{uuid}.
|
||
|
|
7a25983266 |
vector: cover index names in named_segment_fragments (#12161)
vector: cover the index name in named_segment_fragments The planner groups tasks by index name — assert the helper actually returns the logical name so a regression dropping it fails. |
||
|
|
d5433ae745 |
vector: cover the identity object in s3vectors auth (#12162)
vector: cover the identity object in the s3vectors auth context The auth middleware must carry the resolved identity object — only the name survives otherwise and authorization loses actions, account, and claims. |
||
|
|
9b3f09d0a5 |
sftp: refuse mkdir and uploads under a missing folder (#12156)
* sftp: refuse mkdir and uploads under a missing folder * sftp: re-check the parent directory when a buffered upload commits The open-time check cannot catch a parent removed while the upload buffered; the filer would recreate it when the entry lands. * sftp: cover mkdir and upload commit against a missing parent Extend the test filer with CreateEntry so makeDir can be exercised end to end. * sftp: refuse a rename into a missing folder The filer's rename path creates the target entry through CreateEntry, which recreates absent parents the same way uploads did. --------- Co-authored-by: user.email <chrislusf@users.noreply.github.com> |
||
|
|
afb8472c5e |
s3vectors: cover non-filterable key round-trip and nested filter rejection (#12160)
* s3vectors: cover non-filterable key round-trip and nested filter rejection The catalog writes the key list as a JSON string while older records may still carry the array form; indexFromRecord must decode both. The rejection path extracts field names through nested $and/$or filters, so a blocked key deep in the tree still fails the query. * s3vectors: factor the non-filterable rejection into a testable gate Pull the blocked-key check out of handleQueryVectors into nonFilterableField so the rejection itself is covered without standing up the catalog stack; the handler now 400s on whatever the gate names. * s3vectors: report the non-filterable match separately from the key An empty metadata key in nonFilterableMetadataKeys collided with the "no match" sentinel, so a filter on that key skipped rejection. Return a match flag and cover the empty-key case in the test. |
||
|
|
7d823265e5 |
wdclient: regression test for the elapsed caller-deadline lookup path (#12154)
* wdclient: cover a lapsed caller deadline that Err() has not observed yet * wdclient: reuse isCanceledErr for the elapsed-deadline check * wdclient: reinterpret only deadline errors as caller-gave-up isCanceledErr also matches gRPC Canceled, which a filer can return for its own reasons; treating it as an elapsed caller deadline hid the failure from the filer's health record. The fallback now matches only context and gRPC DeadlineExceeded, so filer-side cancellations keep counting as filer failures. |
||
|
|
2b7c65048d |
build(deps): bump com.google.guava:guava from 32.0.0-jre to 33.7.1-jre in /other/java/client (#12147)
* build(deps): bump com.google.guava:guava in /other/java/client Bumps [com.google.guava:guava](https://github.com/google/guava) from 32.0.0-jre to 33.7.1-jre. - [Release notes](https://github.com/google/guava/releases) - [Commits](https://github.com/google/guava/commits) --- updated-dependencies: - dependency-name: com.google.guava:guava dependency-version: 33.7.1-jre dependency-type: direct:production ... Signed-off-by: dependabot[bot] <support@github.com> * other/java/client: bump guava to 33.7.2-jre --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: chrislusf <chrislusf@users.noreply.github.com> |
||
|
|
237db29dca |
vector: round-trip and enforce non-filterable metadata keys (#12157)
vector: non-filterable metadata round-trips and filters reject it - GetIndex omitted nonFilterableMetadataKeys — the catalog stores the list as a JSON string but the reader only accepted an array; both forms now decode. - QueryVectors now rejects filters referencing non-filterable keys instead of silently scanning them. |
||
|
|
190ab32cfb |
vector: weed-vector registers to master; s3 discovers it there (#12146)
* vector: weed-vector registers with the weed master weed-vector-server --masters holds a KeepConnected stream to the weed master, the same mechanism filer and s3 use via wdclient — the server shows up in ListClusterNodes(client_type=vector). Resends registration and rediscovers on failure. * vector: s3 discovers weed-vector through the master With -vectors.grpc unset the vectors gateway resolves one registered weed-vector via ListClusterNodes on the masters it already derives from filer config — discovery replaces the static dial for the single-worker OSS path. The conformance harness registers the engine to mini's master and drops -vectors.grpc. * vector: default-fill registration fields ..Default::default() keeps the source compiling against proto variants that carry extra fields, so the same file works in either tree. * vector: serve the Vectors API on the main s3 port The vectors router sits on the main -port listener behind a dispatch that checks the SigV4 credential scope for /s3vectors/ — no separate port needed. -port.vectors stays for explicit dedicated-port deployments; the conformance harness now hits vectors through :8334. * vector: drop -port.vectors entirely It was new — no compat to preserve. The main -port listener serves the API whenever a weed-vector backend is reachable. * vector: drop -vectors.grpc; discovery is the only locator weed-vector always registers to master, the gateway always resolves it through ListClusterNodes — a static address just forks membership. * vector: binary is weed-vector Matches the enterprise binary; package and lib names are unchanged. * vector: review fixes — cluster type, grpc port, lazy resolve, leader hint, advertise - cluster: register 'vector' as a node type so ListClusterNodes returns weed-vector members - s3: dial masters via ToGrpcAddress (HTTP port + 10000) - s3: backend resolves lazily through vectorDiscovery — workers joining after gateway startup work on first query, and address changes re-dial - master_client: convert master HTTP addr to grpc +10000, honor http(s) scheme, follow the leader hint in KeepConnectedResponse, and --advertise covers wildcard/unreachable listen addresses * vector: no --advertise flag; derive the outbound address A wildcard listen resolves to the source IP the master sees via a route lookup, so registration always advertises something reachable without another flag. * vector: review fixes — plaintext dispatch, conn leaks, discovery backoff The two plaintext http servers now serve the vectors dispatch too — signed s3vectors calls on the main port reach the right router. discoverVectorWorker closes each master conn and bounds lookups to 5s; a dead or absent worker no longer re-hits master on every RPC — the resolver backs off for 5s between rediscovery attempts. * vector: 1 GiB grpc cap, dead-worker invalidation, CLI rename Valid PutVectors batches exceed the tonic 4 MiB default — match the gateway's 1 GiB cap. A replaced worker no longer sticks: any RPC error resets the cached address and the next call rediscovers through the master. The core CLI bin renames to weed-vector-cli so the two crates do not collide on the same output path. * vector: retry-only-transport reset, periodic re-resolve, cli rename - reset() takes the failed client and runs only on transport errors — an InvalidArgument on a healthy conn no longer interrupts unrelated requests, and a concurrent re-resolve wins the race. - cli() re-resolves once the interval passes even with a live client, so a worker that moves addresses is picked up. - verify.rs spike docs use weed-vector-cli after the bin rename. * vector: only Unavailable counts as a transport failure A request deadline or an application error can fire on a healthy connection — resetting the shared conn on those churned reconnects. |
||
|
|
81879fba7b |
vector: preserve typed error chains in lease pin/release (#12153)
vector: keep typed error chains in lease pin/release Error::msg discarded the source chain — denied() could not downcast to object_store::PermissionDenied, so try_pin surfaced the error instead of degrading to an unleased query and read-only credentials broke GetVectors/QueryVectors. Plain ? preserves the chain. |
||
|
|
a2863bf83b |
wdclient: count an elapsed caller deadline as caller-gave-up in LookupVolumeIds (#12151)
* wdclient: treat an elapsed caller deadline as caller-gave-up on lookup errors gRPC can surface its own DeadlineExceeded a tick before ctx.Err() becomes visible, so a lookup that died on the caller's deadline was sporadically recorded as a filer failure (seen in the linux/386 job). * wdclient: reinterpret only deadline errors against an elapsed caller deadline |
||
|
|
67ce9a5f6d |
vector: expose index names in segment planning (#12150)
vector: named_segment_fragments carries the logical index name Sharded builds create one named index per shard and a with_index_segments scan must not mix names — a fanout planner needs the name to group tasks, so expose it alongside uuid and coverage. |
||
|
|
10ba1b782f |
vector: carry the full identity through s3vectors auth (#12152)
vector: keep the full identity in the request context Auth discarded the resolved identity object — the s3tables manager request then re-resolved by name and denied authenticated users their actions, policies, account, and claims. Store the object via SetIdentityInContext so downstream authorization sees it. |
||
|
|
b6851e561f |
build(deps): bump github.com/go-jose/go-jose/v4 from 4.1.5 to 4.1.6 (#12148)
Bumps [github.com/go-jose/go-jose/v4](https://github.com/go-jose/go-jose) from 4.1.5 to 4.1.6. - [Release notes](https://github.com/go-jose/go-jose/releases) - [Commits](https://github.com/go-jose/go-jose/compare/v4.1.5...v4.1.6) --- updated-dependencies: - dependency-name: github.com/go-jose/go-jose/v4 dependency-version: 4.1.6 dependency-type: indirect ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> |
||
|
|
3b65169d6c |
build(deps): bump com.google.guava:guava from 32.1.3-jre to 33.7.1-jre in /test/java/spark (#12141)
* build(deps): bump com.google.guava:guava in /test/java/spark Bumps [com.google.guava:guava](https://github.com/google/guava) from 32.1.3-jre to 33.7.1-jre. - [Release notes](https://github.com/google/guava/releases) - [Commits](https://github.com/google/guava/commits) --- updated-dependencies: - dependency-name: com.google.guava:guava dependency-version: 33.7.1-jre dependency-type: direct:production ... Signed-off-by: dependabot[bot] <support@github.com> * test/java/spark: bump guava to 33.7.2-jre 33.7.1 is covered by a published advisory for uncontrolled memory allocation during CompactHashMap/CompactHashSet deserialization; 33.7.2 is the patched release. * test/random_access: bump guava to 33.7.2-jre Same deserialization advisory; the pin predates the fix. --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: user.email <chrislusf@users.noreply.github.com> |
||
|
|
bb5375d2b9 |
build(deps): bump rustls from 0.23.45 to 0.23.46 in /seaweed-volume (#12144)
Bumps [rustls](https://github.com/rustls/rustls) from 0.23.45 to 0.23.46. - [Release notes](https://github.com/rustls/rustls/releases) - [Changelog](https://github.com/rustls/rustls/blob/main/CHANGELOG.md) - [Commits](https://github.com/rustls/rustls/compare/v/0.23.45...v/0.23.46) --- updated-dependencies: - dependency-name: rustls dependency-version: 0.23.46 dependency-type: direct:production ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> |
||
|
|
89cc910a86 |
helm: render the filer's mysql credentials once when the user sets them (#12139)
* helm: render the filer's mysql credentials once when the user sets them With the MySQL filer store, the filer reads WEED_MYSQL_USERNAME and WEED_MYSQL_PASSWORD from the chart's db secret. Setting either one in extraEnvironmentVars or secretExtraEnvironmentVars, as helm_ci.yml does for the password, added a second entry with the same name: Helm 4 rejects the install (env: duplicate entries for key), and Helm 3 deploys with a warning that one definition may be dropped on apply. The db secret's entry is now left out for a key the user sets, so each key is rendered once and the user's value wins, the rule #8488 applied to a key set in both global and filer extraEnvironmentVars. Without an override the filer StatefulSet is unchanged. * helm: render the selected filer mysql credential ahead of the merged vars A credential the user sets now renders in the early position the db secret's entries used to occupy, and is excluded from the merged and secret loops. Kubernetes resolves a $(VAR) reference only against names declared earlier, so a later entry such as WEED_MYSQL_DSN can again reference the active credential. Secret-backed values win over plain ones; the db secret's entry remains the fallback. * helm: let a secret-backed filer env key replace the plain one secretExtraEnvironmentVars now wins over extraEnvironmentVars for the same name, rendering once at the merged key's sorted position instead of twice. The values.yaml WEED_MYSQL_* defaults no longer duplicate when overridden through the secret map - e.g. enabling the store via a secret WEED_MYSQL_ENABLED also rendered the "false" default before, which Helm 4's server-side apply rejects as a duplicate. * helm: keep a credential that references another env var in sorted position A user-supplied credential whose value itself holds a $(...) reference stays in the merged vars' sorted order so its own references still resolve; only reference-free values, valueFrom entries, and the db-secret defaults move to the early slot. * helm: render vars a filer mysql credential references ahead of it Hoisting the selected WEED_MYSQL_USERNAME/PASSWORD entries above the merged extra vars let WEED_MYSQL_DSN-style $(...) references resolve, but it broke the opposite direction: a user-set credential such as WEED_MYSQL_USERNAME: "$(DB_USER)" was emitted before DB_USER, so Kubernetes left the reference literal. Pull every merged or secret var a credential value transitively references out of the sorted set and emit them first, dependencies before dependents, so both directions resolve. * helm: emit filer mysql credentials after the chart's own env vars The dependency-ordered credential block rendered before SEAWEEDFS_FULLNAME and the s3 credentials, so a user-supplied WEED_MYSQL_USERNAME could not reference them via $(...). Move the block below those fixed entries so references in either direction resolve. * helm: bound filer mysql credential ordering passes by var count Chains deeper than the fixed eight passes forced a credential out ahead of the vars it references. Cap discovery at the number of merged/secret vars and emit passes at the ordered list length, so any resolvable chain finishes before the last-pass fallback emits cyclic leftovers. --------- Co-authored-by: chrislusf <chrislusf@users.noreply.github.com> |
||
|
|
3a0af308ce |
helm: keep the filer db credentials across uninstall and install (#12138)
* helm: keep the filer db credentials across uninstall and install The db secret is kept on uninstall (`helm.sh/resource-policy: keep`), but it is a pre-install hook, and Helm deletes an existing hook resource before creating it again. The next install therefore wrote a new random password and reset the user to YourSWUser, so the filer could no longer log in to a database set up with the old ones, including credentials edited into the kept Secret. The template now reads the existing Secret with lookup and reuses its user and password; a first install still generates them as before. A kind step in helm_ci.yml installs, edits the Secret, uninstalls, installs again and checks both values. * helm: README, the db secret is kept and reused across a reinstall * helm: make the db secret hook-delete-policy explicit before-hook-creation is Helm's default for hook resources, but spell it out so the reuse contract is obvious: lookup reads the kept Secret at render time, the old copy is deleted before the hook recreates it, and the stored credentials carry over. --------- Co-authored-by: Chris Lu <chris.lu@gmail.com> |
||
|
|
1e9cc44697 |
build(deps): bump rustls from 0.23.45 to 0.23.46 in /seaweed-worker (#12142)
Bumps [rustls](https://github.com/rustls/rustls) from 0.23.45 to 0.23.46. - [Release notes](https://github.com/rustls/rustls/releases) - [Changelog](https://github.com/rustls/rustls/blob/main/CHANGELOG.md) - [Commits](https://github.com/rustls/rustls/compare/v/0.23.45...v/0.23.46) --- updated-dependencies: - dependency-name: rustls dependency-version: 0.23.46 dependency-type: indirect ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> |
||
|
|
f16f234c53 |
build(deps): bump org.xerial.snappy:snappy-java from 1.1.10.4 to 1.1.10.9 in /test/java/spark (#12143)
build(deps): bump org.xerial.snappy:snappy-java in /test/java/spark Bumps [org.xerial.snappy:snappy-java](https://github.com/xerial/snappy-java) from 1.1.10.4 to 1.1.10.9. - [Release notes](https://github.com/xerial/snappy-java/releases) - [Commits](https://github.com/xerial/snappy-java/compare/v1.1.10.4...v1.1.10.9) --- updated-dependencies: - dependency-name: org.xerial.snappy:snappy-java dependency-version: 1.1.10.9 dependency-type: direct:production ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> |
||
|
|
7b0e57eada |
build(deps): bump github.com/go-jose/go-jose/v4 from 4.1.5 to 4.1.6 in /test/kafka (#12140)
build(deps): bump github.com/go-jose/go-jose/v4 in /test/kafka Bumps [github.com/go-jose/go-jose/v4](https://github.com/go-jose/go-jose) from 4.1.5 to 4.1.6. - [Release notes](https://github.com/go-jose/go-jose/releases) - [Commits](https://github.com/go-jose/go-jose/compare/v4.1.5...v4.1.6) --- updated-dependencies: - dependency-name: github.com/go-jose/go-jose/v4 dependency-version: 4.1.6 dependency-type: indirect ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> |
||
|
|
a1f6621553 | docs: regenerate star history chart | ||
|
|
ef16b4ed02 |
helm: render numbers and booleans in extraEnvironmentVars as values (#12137)
Only a string rendered as `value`; anything else went to `valueFrom`. An unquoted number or boolean in a values file or `--set`, such as `WEED_MYSQL_PORT: 3306` or `WEED_MYSQL_ENABLED=true`, rendered as `valueFrom: 3306` and the API server rejected the workload. A map still renders as `valueFrom`, a null still renders an empty variable, and strings are unchanged. Numbers go through toJson because values files decode them as float64, and quote would print 1000000 as 1e+06. |
||
|
|
5f26ef6ad1 |
filer,webdav,iam,sftp: add -idleTimeout flag for connection idle timeout (#12136)
* filer: make the HTTP connection idle timeout configurable The filer applied a hardcoded 10s activity timeout to its HTTP listener. A buffering proxy in front of the filer can stall upstream reads longer than that when the end client drains slowly, so large downloads through it were truncated (#8062). Expose the value as -idleTimeout (default 10, unchanged), matching the existing -idleTimeout flags on the volume and s3 servers, and wire filer.idleTimeout through 'weed server' and 'weed mini' too. A nil option keeps the 10s default so embedders constructing FilerOptions directly keep working. * filer: apply the idle timeout to the readonly port too The -port.readonly listener ran without any connection timeout while the main API port timed out. Give it the same -idleTimeout so both ports behave identically. * webdav: make the HTTP connection idle timeout configurable The WebDAV server used the same hardcoded 10s activity timeout. Expose it as -idleTimeout (default 10, unchanged), including -webdav.idleTimeout when embedded via 'weed filer -webdav', 'weed server -webdav', and 'weed mini'. * iam: make the HTTP connection idle timeout configurable The IAM API server used the same hardcoded 10s activity timeout. Expose it as -idleTimeout (default 10, unchanged), including -iam.idleTimeout when embedded via 'weed filer -iam' and 'weed server -iam'. * sftp: make the connection idle timeout configurable The SFTP server used the same hardcoded 10s activity timeout. Expose it as -idleTimeout (default 10, unchanged), including -sftp.idleTimeout when embedded via 'weed filer -sftp' and 'weed server -sftp'. * command: clamp idle timeout seconds before converting to duration A seconds value past time.Duration's range would overflow on multiplication and silently disable the timeout. Bound it in one shared helper, which also deduplicates the nil-default handling at the four call sites. * command: return zero for non-positive idle timeout seconds A large negative seconds value could wrap on multiplication and come out positive. Guard before converting so non-positive input always disables the timeout. * command: apply idle timeout to local unix socket listeners The unix-socket listeners in filer, s3, and sftp served accepted connections with no activity deadline while the TCP listeners already wrapped conns in util.Listener. Wrap the socket listeners the same way so a positive idleTimeout also bounds idle socket connections. * command: clamp s3 idle timeout seconds before multiplying s3 still multiplied the flag inline, so an out-of-range value could overflow time.Duration. Route it through idleTimeoutSeconds like the other commands; non-positive keeps meaning disabled. * volume: clamp idle connection timeout seconds before converting * command: cover idleTimeoutSeconds conversion semantics * command: gate idleTimeoutSeconds clamp check on 64-bit int |
||
|
|
6536fac9bd |
s3: answer NoSuchBucket for encryption and policy writes to a missing bucket (#12133)
* s3: answer NoSuchBucket for encryption and policy writes to a missing bucket PutBucketEncryption, DeleteBucketEncryption, and PutBucketPolicy surfaced the filer's bucket-miss as InternalError; GetBucketEncryption masked it as ServerSideEncryptionConfigurationNotFoundError. Map the miss to NoSuchBucket instead, matching AWS. Internal readers still treat a missing bucket as "no encryption configured" so uploads keep their behavior. * s3: wrap ErrBucketNotFound only for verified misses in bucket policy ops The filer lookup helpers in get/set/deleteBucketPolicy marked every LookupDirectoryEntry failure as bucket-not-found, so a transient filer outage answered NoSuchBucket for a live bucket. Route them through filer_pb.LookupEntry, which normalizes only true misses to ErrNotFound. * s3: answer NoSuchBucket when the policy delete's own write loses the bucket * s3: classify a filer NotFound on the policy UpdateEntry as ErrBucketNotFound * s3: require the bucket entry to exist on config patch transactions * s3: answer NoSuchBucket when tagging and CORS writes lose the bucket mid-request * s3: keep the existence check on no-op bucket config patches An unchanged config write produced an empty patch that early-returned before the transaction, so a bucket deleted between the read and the write still reported success. Ship the condition-only transaction so IF_EXISTS runs on its own. * s3: guard the iam step and cover failure-vs-miss classification Nil-guard s3a.iam in the bucket policy handlers so the handlers do not panic when no IAM server is wired (unit tests, embedded servers), treat a "precondition failed" response string the same as the error code when mapping a failed patch to ErrNotFound, and add handler-level tests that pin the miss/failure distinction: missing buckets and vanished mid-write buckets answer 404 NoSuchBucket while transient filer failures stay 500 InternalError. |
||
|
|
4c9e62177d |
s3: keep multipart part encryption on the initiate-time decision (#12134)
* s3: keep multipart part encryption on the initiate-time decision putToFiler applied the *current* bucket default encryption when writing a staged part, so a PutBucketEncryption landing between two UploadPart calls produced an object whose parts disagree: some chunks ciphertext, some plaintext. Completion then marks the object by its majority and GetObject fails decrypting the rest. Parts staged under .uploads now skip the bucket-default lookup; the decision snapshotted into the upload entry at CreateMultipartUpload is replayed through request headers instead, which matches AWS (default encryption applies at initiation). * s3: mark multipart part writes via request context, not the staged path Inferring "is a part" from the .uploads file path let an ordinary PutObject named .uploads/x.part skip bucket default encryption entirely. The part decision now travels on the request context from the two callers that stage parts (UploadPart, UploadPartCopy); a client-chosen key cannot reach it. |