mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-08-16 20:26:45 +00:00
* s3tables: purge decoupled table data without deleting the reused name path A renamed or created-over-leftover table keeps its data at a location that differs from its catalog name path. Drop now purges that data location and clears the marker, instead of recursively deleting the name path, which may still hold another table's data. * iceberg: route a table created over a leftover to a unique location When the default location is occupied by a leftover directory (data kept when another table was renamed to this name), create the new table at a unique location so it cannot overwrite that table's metadata. Common case is unchanged. * iceberg: fail table create when the leftover-path check errors A transient filer lookup error fell through as "not occupied", routing the new table back to the default path and risking the very overwrite this check guards against. Propagate the error and return 500 instead. * s3tables: assert all catalog xattrs cleared on decoupled drop Seed the full marker set so the test catches a regression that leaves the policy, tags, version, or entry-type attribute on the reused name path. * s3tables: refuse to drop a table whose data path is an ancestor Corrupt metadata can resolve the data path to the bucket or namespace root, which the bucket-scope check still admits; a recursive purge there would wipe sibling tables. Reject an ancestor data path before deleting.
95 lines
4.1 KiB
Go
95 lines
4.1 KiB
Go
package s3tables
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func runDeleteTable(t *testing.T, m *Manager, fs *memFilerServer, namespace, name string) error {
|
|
t.Helper()
|
|
return m.Execute(context.Background(), NewManagerClient(fs.client), "DeleteTable", &DeleteTableRequest{
|
|
TableBucketARN: mustBucketARN(t),
|
|
Namespace: []string{namespace},
|
|
Name: name,
|
|
}, nil, "")
|
|
}
|
|
|
|
// A table whose data was decoupled from its name (created over a leftover from a
|
|
// rename): catalog marker at ns/newt, data at ns/newt-x, and ns/newt still holds
|
|
// another table's leftover data. Dropping it must purge only its own data and
|
|
// clear the marker, never the data under the reused name path.
|
|
func TestDeleteTableDecoupledKeepsReusedNamePath(t *testing.T) {
|
|
fs, m := startRenameManager(t)
|
|
|
|
newtMeta, _ := json.Marshal(tableMetadataInternal{
|
|
Name: "newt",
|
|
Namespace: "ns",
|
|
Format: "ICEBERG",
|
|
OwnerAccountID: DefaultAccountID,
|
|
MetadataLocation: "s3://" + renameTestBucket + "/ns/newt-x/metadata/v1.metadata.json",
|
|
})
|
|
markerKeys := []string{ExtendedKeyMetadata, ExtendedKeyMetadataVersion, ExtendedKeyPolicy, ExtendedKeyTags, ExtendedKeyEntryType}
|
|
fs.putEntry(GetNamespacePath(renameTestBucket, "ns"), "newt", map[string][]byte{
|
|
ExtendedKeyMetadata: newtMeta,
|
|
ExtendedKeyMetadataVersion: []byte("v1"),
|
|
ExtendedKeyPolicy: []byte(`{"Version":"2012-10-17"}`),
|
|
ExtendedKeyTags: []byte(`{"k":"v"}`),
|
|
ExtendedKeyEntryType: []byte(EntryTypeTable),
|
|
})
|
|
fs.putEntry(GetTablePath(renameTestBucket, "ns", "newt"), "leftover", nil) // another table's data under the name path
|
|
fs.putEntry(GetNamespacePath(renameTestBucket, "ns"), "newt-x", nil) // this table's own (decoupled) data
|
|
fs.putEntry(GetTablePath(renameTestBucket, "ns", "newt-x"), "metadata", nil)
|
|
|
|
require.NoError(t, runDeleteTable(t, m, fs, "ns", "newt"))
|
|
|
|
assert.Nil(t, fs.getEntry(GetNamespacePath(renameTestBucket, "ns"), "newt-x"),
|
|
"the table's own data location must be purged")
|
|
assert.NotNil(t, fs.getEntry(GetTablePath(renameTestBucket, "ns", "newt"), "leftover"),
|
|
"data under the reused name path must survive")
|
|
marker := fs.getEntry(GetNamespacePath(renameTestBucket, "ns"), "newt")
|
|
require.NotNil(t, marker)
|
|
for _, key := range markerKeys {
|
|
_, present := marker.Extended[key]
|
|
assert.Falsef(t, present, "catalog attribute %s must be cleared", key)
|
|
}
|
|
}
|
|
|
|
// A table whose MetadataLocation resolves to an ancestor of its own name path
|
|
// (here the namespace root, e.g. from corrupt metadata) must not be deleted: a
|
|
// recursive purge of that data path would wipe sibling tables. The delete is
|
|
// refused and the namespace's other tables survive.
|
|
func TestDeleteTableRefusesAncestorDataPath(t *testing.T) {
|
|
fs, m := startRenameManager(t)
|
|
|
|
badMeta, _ := json.Marshal(tableMetadataInternal{
|
|
Name: "badt",
|
|
Namespace: "ns",
|
|
Format: "ICEBERG",
|
|
OwnerAccountID: DefaultAccountID,
|
|
MetadataLocation: "s3://" + renameTestBucket + "/ns/metadata/v1.metadata.json",
|
|
})
|
|
fs.putEntry(GetNamespacePath(renameTestBucket, "ns"), "badt", map[string][]byte{ExtendedKeyMetadata: badMeta})
|
|
|
|
require.Error(t, runDeleteTable(t, m, fs, "ns", "badt"))
|
|
|
|
// The sibling table seeded by startRenameManager and its data must survive.
|
|
assert.NotNil(t, fs.getEntry(GetNamespacePath(renameTestBucket, "ns"), "t"),
|
|
"sibling table marker must survive a refused delete")
|
|
assert.NotNil(t, fs.getEntry(GetTablePath(renameTestBucket, "ns", "t"), "data"),
|
|
"sibling table data must survive a refused delete")
|
|
}
|
|
|
|
// A normal colocated table (data under its own name path) is removed wholesale.
|
|
func TestDeleteTableColocatedRemovesData(t *testing.T) {
|
|
fs, m := startRenameManager(t)
|
|
|
|
require.NoError(t, runDeleteTable(t, m, fs, "ns", "t"))
|
|
|
|
assert.Nil(t, fs.getEntry(GetNamespacePath(renameTestBucket, "ns"), "t"), "colocated table entry must be deleted")
|
|
assert.Nil(t, fs.getEntry(GetTablePath(renameTestBucket, "ns", "t"), "metadata"), "colocated table data must be deleted")
|
|
}
|