mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-08-20 22:27:04 +00:00
fix(s3/lifecycle): WalkerDispatcher uses entry.Path for ABORT_MPU + shell announces load
Two CI-surfaced bugs caught by PR #9471's S3 Lifecycle Tests run on master after PRs #9475 + #9466: 1. Walker dispatch for ABORT_MPU was sending entry.DestKey as req.ObjectPath. The server's ABORT_MPU handler (weed/s3api/s3api_internal_lifecycle.go) strips the .uploads/ prefix to extract the upload id and reads the init record from that directory, so it expects the .uploads/<id> path verbatim. DestKey looks like a regular object path; the server's prefix check fails and the dispatch returns BLOCKED with "FATAL_EVENT_ERROR: ABORT_MPU object_path missing .uploads/ prefix". The test fix renames TestWalkerDispatcher_MPUInitUsesDestKey to ...UsesUploadsPath and inverts the assertion to match the actual server contract. DestKey is still used for the WalkBuckets shard predicate and for rule-prefix matching in bootstrap.walker; both surfaces want the user's intended path, while DISPATCH wants the .uploads/<id> directory. The bootstrap test (TestLifecycleAbortIncompleteMultipartUpload) caught this when the walker's BLOCKED error surfaced as FATAL output. 2. test/s3/lifecycle/s3_lifecycle_empty_bucket_test.go asserts the shell command logs "loaded lifecycle for N bucket(s)" so a regression that produces half-shaped output (no load summary) is caught. The restored shell command (PR #9475) didn't print that line; add it back on the first pass that finds non-zero inputs.
This commit is contained in:
@@ -35,16 +35,18 @@ func (d *WalkerDispatcher) Delete(ctx context.Context, action *engine.CompiledAc
|
||||
}
|
||||
objectPath := entry.Path
|
||||
if entry.IsMPUInit {
|
||||
// Rule-prefix matching used DestKey; the server takes the
|
||||
// canonical object path for the LifecycleDelete RPC, which
|
||||
// is also DestKey. The walker hits the .uploads/<id>
|
||||
// directory itself only when ActionKind=ABORT_MPU, and the
|
||||
// server resolves the upload from (bucket, object_path) +
|
||||
// the init record's metadata.
|
||||
// Rule-prefix matching uses DestKey (the user's intended
|
||||
// object key); dispatch uses entry.Path (.uploads/<id>),
|
||||
// which is what the server's ABORT_MPU handler expects in
|
||||
// req.ObjectPath — it strips the .uploads/ prefix to get
|
||||
// the upload id and reads the init record from that
|
||||
// directory. DestKey is the dispatch ANTI-pattern here: it
|
||||
// looks like a regular object path, the server's check for
|
||||
// the .uploads/ prefix fails, and the dispatch comes back
|
||||
// as BLOCKED FATAL_EVENT_ERROR.
|
||||
if entry.DestKey == "" {
|
||||
return fmt.Errorf("walker dispatch: MPU init entry with empty DestKey: %s", entry.Path)
|
||||
}
|
||||
objectPath = entry.DestKey
|
||||
}
|
||||
rh := action.Key.RuleHash
|
||||
req := &s3_lifecycle_pb.LifecycleDeleteRequest{
|
||||
|
||||
@@ -72,9 +72,11 @@ func TestWalkerDispatcher_VersionedPassesVersionID(t *testing.T) {
|
||||
assert.Equal(t, "v-abc", c.lastReq.VersionId)
|
||||
}
|
||||
|
||||
func TestWalkerDispatcher_MPUInitUsesDestKey(t *testing.T) {
|
||||
// Rule-prefix matching used DestKey; the RPC ObjectPath must match
|
||||
// so the server resolves the upload from the user's intended key.
|
||||
func TestWalkerDispatcher_MPUInitUsesUploadsPath(t *testing.T) {
|
||||
// Rule-prefix matching uses DestKey to decide IF this MPU init
|
||||
// matches; dispatch uses Path (.uploads/<id>) because the server's
|
||||
// ABORT_MPU handler strips the .uploads/ prefix to get the upload
|
||||
// id. Sending DestKey here would BLOCK with FATAL_EVENT_ERROR.
|
||||
c := &walkerStubClient{outcome: s3_lifecycle_pb.LifecycleDeleteOutcome_DONE}
|
||||
d := &WalkerDispatcher{Client: c}
|
||||
a := sampleAction(t, s3lifecycle.ActionKindAbortMPU)
|
||||
@@ -84,7 +86,7 @@ func TestWalkerDispatcher_MPUInitUsesDestKey(t *testing.T) {
|
||||
IsMPUInit: true,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "user/path/object", c.lastReq.ObjectPath)
|
||||
assert.Equal(t, ".uploads/abc123", c.lastReq.ObjectPath)
|
||||
}
|
||||
|
||||
func TestWalkerDispatcher_MPUInitEmptyDestKeyErrors(t *testing.T) {
|
||||
|
||||
@@ -122,6 +122,7 @@ func (c *commandS3LifecycleRunShard) Do(args []string, env *CommandEnv, writer i
|
||||
fmt.Fprintf(writer, "running shards %s (event budget=%d, runtime=%s, refresh=%s)…\n",
|
||||
formatShardLabel(shards), *eventBudget, *runtime, *cadence)
|
||||
|
||||
announcedLoad := false
|
||||
runPass := func() error {
|
||||
eng := engine.New()
|
||||
inputs, parseErrors, err := scheduler.LoadCompileInputs(ctx, filerClient, bucketsPath)
|
||||
@@ -140,6 +141,10 @@ func (c *commandS3LifecycleRunShard) Do(args []string, env *CommandEnv, writer i
|
||||
if len(inputs) == 0 {
|
||||
return nil
|
||||
}
|
||||
if !announcedLoad {
|
||||
fmt.Fprintf(writer, "loaded lifecycle for %d bucket(s)\n", len(inputs))
|
||||
announcedLoad = true
|
||||
}
|
||||
buckets := make([]string, 0, len(inputs))
|
||||
for _, in := range inputs {
|
||||
if in.Bucket != "" {
|
||||
|
||||
Reference in New Issue
Block a user