diff --git a/cmd/metacache-server-pool.go b/cmd/metacache-server-pool.go index bcdfed8f10ff4..8e23f811a7e30 100644 --- a/cmd/metacache-server-pool.go +++ b/cmd/metacache-server-pool.go @@ -71,13 +71,13 @@ func (z *erasureServerPools) listPath(ctx context.Context, o *listPathOptions) ( if o.Marker != "" && o.Prefix != "" { // Marker not common with prefix is not implemented. Send an empty response if !HasPrefix(o.Marker, o.Prefix) { - return entries, io.EOF + return entries, z.listPathShortcutEOF(ctx, o.Bucket) } } // With max keys of zero we have reached eof, return right here. if o.Limit == 0 { - return entries, io.EOF + return entries, z.listPathShortcutEOF(ctx, o.Bucket) } // For delimiter and prefix as '/' we do not list anything at all @@ -85,7 +85,7 @@ func (z *erasureServerPools) listPath(ctx context.Context, o *listPathOptions) ( // as '/' we don't have any entries, since all the keys are // of form 'keyName/...' if strings.HasPrefix(o.Prefix, SlashSeparator) { - return entries, io.EOF + return entries, z.listPathShortcutEOF(ctx, o.Bucket) } // If delimiter is slashSeparator we must return directories of @@ -254,6 +254,21 @@ func (z *erasureServerPools) listPath(ctx context.Context, o *listPathOptions) ( return entries, nil } +// listPathShortcutEOF is used by the listPath shortcuts that return an empty +// result without ever consulting the storage layer (a slash-only prefix, a +// zero limit, or a marker outside the prefix). Because they bypass the per-set +// listing - which is what surfaces a missing bucket as errVolumeNotFound - they +// must verify bucket existence explicitly, otherwise a listing on a +// non-existent bucket is masked as an empty result. AWS S3 returns NoSuchBucket +// regardless of the prefix/marker/max-keys supplied. +// See https://github.com/pgsty/minio/issues/32 +func (z *erasureServerPools) listPathShortcutEOF(ctx context.Context, bucket string) error { + if _, err := z.GetBucketInfo(ctx, bucket, BucketOptions{}); err != nil { + return err + } + return io.EOF +} + // listMerged will list across all sets and return a merged results stream. // The result channel is closed when no more results are expected. func (z *erasureServerPools) listMerged(ctx context.Context, o listPathOptions, results chan<- metaCacheEntry) error { diff --git a/cmd/object_api_suite_test.go b/cmd/object_api_suite_test.go index 3377ad208d2f5..9d9bf96814232 100644 --- a/cmd/object_api_suite_test.go +++ b/cmd/object_api_suite_test.go @@ -849,6 +849,41 @@ func testListObjectsTestsForNonExistentBucket(obj ObjectLayer, instanceType stri } } +// Wrapper for calling testListObjectsWithPrefixForNonExistentBucket for both Erasure and FS. +func TestListObjectsWithPrefixForNonExistentBucket(t *testing.T) { + ExecObjectLayerTest(t, testListObjectsWithPrefixForNonExistentBucket) +} + +// Tests validate that ListObjects on a non-existent bucket fails with +// BucketNotFound even when a prefix (or other listing shortcut) is supplied. +// AWS S3 returns NoSuchBucket regardless of the prefix/marker/max-keys values. +// Regression: https://github.com/pgsty/minio/issues/32 +func testListObjectsWithPrefixForNonExistentBucket(obj ObjectLayer, instanceType string, t TestErrHandler) { + testCases := []struct { + prefix string + marker string + maxKeys int + }{ + {prefix: "/", maxKeys: 1000}, // slash-only prefix shortcut (the issue reproduction) + {prefix: "foo/bar", maxKeys: 1000}, // regular prefix + {prefix: "obj", maxKeys: 1}, // max-keys==1 shortcut + {prefix: "obj", maxKeys: 0}, // zero-limit shortcut + {prefix: "a", marker: "b", maxKeys: 1000}, // marker outside the prefix shortcut + } + for i, tc := range testCases { + result, err := obj.ListObjects(context.Background(), "bucket", tc.prefix, tc.marker, "", tc.maxKeys) + if err == nil { + t.Fatalf("%s: test %d (prefix %q): Expected error but found nil.", instanceType, i+1, tc.prefix) + } + if len(result.Objects) != 0 { + t.Fatalf("%s: test %d (prefix %q): Expected 0 objects, but found %d", instanceType, i+1, tc.prefix, len(result.Objects)) + } + if err.Error() != "Bucket not found: bucket" { + t.Errorf("%s: test %d (prefix %q): Expected error msg `%s`, but found `%s`", instanceType, i+1, tc.prefix, "Bucket not found: bucket", err.Error()) + } + } +} + // Wrapper for calling testNonExistentObjectInBucket for both Erasure and FS. func TestNonExistentObjectInBucket(t *testing.T) { ExecObjectLayerTest(t, testNonExistentObjectInBucket)