Some checks failed
go: build dev binaries / cleanup (push) Successful in 4s
Codespell / Check for spelling errors (push) Failing after 3s
docker: build dev containers / build-rust-binaries (amd64, x86_64-unknown-linux-musl) (push) Failing after 3s
docker: build dev containers / build-dev-containers (push) Has been skipped
docker: build dev containers / build-rust-binaries (arm64, true, aarch64-unknown-linux-musl) (push) Failing after 3s
End to End / FUSE Mount (push) Failing after 2s
EC Integration Tests / EC Integration Tests (push) Failing after 30s
FUSE DLM Integration Tests / FUSE DLM Integration Tests (push) Failing after 3s
FUSE Volume Server Failover Tests / FUSE Volume Server Failover (push) Failing after 3s
FUSE Integration Tests / FUSE Integration Testing (push) Failing after 2s
FUSE P2P Peer Chunk Sharing Integration Tests / FUSE P2P Peer Chunk Sharing (push) Failing after 3s
go: build binary / Go Vet (push) Failing after 2s
go: build binary / Go Vet 32-bit (push) Failing after 3s
go: build binary / Build (push) Failing after 4s
go: build binary / Build cross-platform (freebsd/amd64) (push) Failing after 3s
go: build binary / Build cross-platform (windows/amd64) (push) Failing after 3s
go: build binary / Build cross-platform (darwin/arm64) (push) Failing after 3s
go: build binary / Build cross-platform (windows/arm64) (push) Failing after 3s
go: build binary / Test (push) Failing after 2s
go: build binary / Test 32-bit (push) Failing after 3s
helm: lint and test charts / lint-test (push) Failing after 3s
Java Client Integration Tests / Java Integration Tests (11) (push) Failing after 3s
Java Client Integration Tests / Java Integration Tests (17) (push) Failing after 3s
go: build dev binaries / build_dev_linux_windows (amd64, linux) (push) Failing after 2s
go: build dev binaries / build_dev_linux_windows (amd64, windows) (push) Failing after 3s
go: build dev binaries / build_dev_darwin (amd64, darwin) (push) Has been skipped
go: build dev binaries / build_dev_darwin (arm64, darwin) (push) Has been skipped
Java Client Unit Tests / Java Unit Tests (11) (push) Failing after 3s
Java Client Unit Tests / Java Unit Tests (17) (push) Failing after 2s
Java Client Unit Tests / Java Unit Tests (21) (push) Failing after 3s
Java Client Unit Tests / Java Unit Tests (8) (push) Failing after 3s
Kafka Quick Test (Load Test with Schema Registry) / Kafka Client Load Test (Quick) (push) Failing after 3s
Kafka Gateway Tests / Kafka Unit Tests (unit-tests-1) (push) Failing after 11s
Kafka Gateway Tests / Kafka Integration Tests (Critical) (integration-1) (push) Failing after 3s
Kafka Gateway Tests / Kafka End-to-End Tests (with SMQ) (e2e-1) (push) Failing after 3s
Kafka Gateway Tests / Kafka Consumer Group Tests (Highly Isolated) (consumer-group-1) (push) Failing after 2s
Kafka Gateway Tests / Kafka Client Compatibility (with SMQ) (client-compat-1) (push) Failing after 3s
Kafka Gateway Tests / Kafka SMQ Integration Tests (Full Stack) (smq-integration-1) (push) Failing after 3s
Kafka Gateway Tests / Kafka Protocol Tests (Isolated) (protocol-1) (push) Failing after 3s
KMS Tests / KMS Provider Integration Tests (push) Failing after 2s
KMS Tests / S3 KMS End-to-End Tests (push) Failing after 3s
Master Cold Start Tests / Master Cold Start Tests (push) Failing after 4s
Metadata Subscribe Integration Tests / Metadata Subscribe Integration Tests (push) Failing after 3s
Multi-Master Tests / Multi-Master Failover Tests (push) Failing after 3s
Performance / CPU and Heap Profile (push) Failing after 2s
Performance / S3 Read/Write Benchmark (rust volume) (push) Failing after 3s
pjdfstest POSIX Compliance / pjdfstest (push) Failing after 2s
Plugin Worker Integration Tests / Plugin Worker: erasure_coding (push) Failing after 3s
Plugin Worker Integration Tests / Plugin Worker: vacuum (push) Failing after 3s
Rust Volume Server Tests / Rust Integration Tests (push) Failing after 3s
Rust Volume Server Tests / Go Tests with Rust Volume (grpc - Shard 1) (push) Failing after 2s
Rust Volume Server Tests / Go Tests with Rust Volume (http - Shard 1) (push) Failing after 3s
Rust Volume Server Tests / Go Tests with Rust Volume (grpc - Shard 2) (push) Failing after 3s
Rust Volume Server Tests / Go Tests with Rust Volume (http - Shard 2) (push) Failing after 3s
Rust Volume Server Tests / Go Tests with Rust Volume (grpc - Shard 3) (push) Failing after 3s
S3 IAM Integration Tests / S3 IAM Distributed Tests (push) Failing after 2s
S3 IAM Integration Tests / S3 IAM Performance Tests (push) Failing after 3s
Performance / Throughput Benchmark (go volume) (push) Failing after 3s
Performance / Throughput Benchmark (rust volume) (push) Failing after 2s
Performance / S3 Read/Write Benchmark (go volume) (push) Failing after 3s
Plugin Worker Integration Tests / Plugin Worker: volume_balance (push) Failing after 3s
PostgreSQL Gateway Tests / PostgreSQL Basic Tests (push) Failing after 3s
Rust Volume Server Tests / Rust Unit Tests (push) Failing after 2s
Rust Volume Server Tests / Go Tests with Rust Volume (http - Shard 3) (push) Failing after 3s
Rust Plugin Worker Tests / Rust Plugin Worker Build and Unit Tests (push) Failing after 3s
rust: build dev volume server binaries / cleanup (push) Successful in 3s
S3 ETag and ACL Tests / S3 ETag + ACL Integration Tests (push) Failing after 3s
S3 IAM Integration Tests / IAM Unit Tests (push) Failing after 3s
S3 IAM Integration Tests / S3 IAM Integration Tests (advanced) (push) Failing after 3s
S3 IAM Integration Tests / S3 IAM Integration Tests (basic) (push) Failing after 2s
S3 IAM Integration Tests / S3 IAM Integration Tests (group) (push) Failing after 3s
S3 IAM Integration Tests / S3 IAM Integration Tests (policy-enforcement) (push) Failing after 2s
S3 IAM Integration Tests / S3 IAM Integration Tests (sts) (push) Failing after 4s
S3 Keycloak Integration Tests / S3 Keycloak Integration Tests (push) Failing after 3s
S3 PyArrow Parquet Tests / PyArrow Parquet Tests (Python 3.11) (push) Failing after 3s
S3 PyArrow Parquet Tests / Go Unit Tests (Implicit Directory) (push) Has been cancelled
S3 PyArrow Parquet Tests / PyArrow Parquet Tests (Python 3.12) (push) Has been cancelled
rust: build dev volume server binaries / build-rust-volume-dev-darwin (darwin-amd64, x86_64-apple-darwin) (push) Has been cancelled
rust: build dev volume server binaries / build-rust-volume-dev-darwin (darwin-arm64, aarch64-apple-darwin) (push) Has been cancelled
rust: build dev volume server binaries / build-rust-volume-dev-linux (linux-amd64, x86_64-unknown-linux-gnu) (push) Has been cancelled
S3 PyArrow Parquet Tests / PyArrow Parquet Tests (Python 3.9) (push) Has been cancelled
S3 Proxy Signature Tests / S3 Proxy Signature Verification Tests (push) Has been cancelled
S3 SDK V2 Route Disambiguation Tests / S3 SDK V2 Routing Tests (push) Has been cancelled
S3 SSE Tests / S3 SSE Integration Tests (quick) (push) Has been cancelled
S3 SSE Tests / S3 SSE Compatibility Test (push) Has been cancelled
S3 SSE Tests / S3 SSE Metadata Persistence Test (push) Has been cancelled
S3 SSE Tests / S3 SSE Copy Operations Test (push) Has been cancelled
S3 SSE Tests / S3 SSE Multipart Upload Test (push) Has been cancelled
S3 SSE Tests / S3 SSE Performance Test (push) Has been cancelled
S3 SSE Tests / S3 Volume Encryption Test (push) Has been cancelled
S3 SSE Tests / S3 SSE Integration Tests (comprehensive) (push) Has been cancelled
Spark Integration Tests / Spark Integration Tests (push) Failing after 2s
Telemetry Integration Tests / telemetry-integration-test (push) Failing after 3s
Volume Server Integration Tests / Volume Server Integration Tests (grpc - Shard 1) (push) Failing after 3s
Volume Server Integration Tests / Volume Server Integration Tests (http - Shard 1) (push) Failing after 3s
Ceph S3 tests / Basic S3 tests (KV store) (push) Has been cancelled
Ceph S3 tests / S3 Versioning & Object Lock tests (push) Has been cancelled
S3 Policy Integration Tests / S3 Policy Unit Tests (push) Failing after 3s
Ceph S3 tests / S3 CORS tests (push) Has been cancelled
Ceph S3 tests / SeaweedFS Custom S3 Copy tests (push) Has been cancelled
Ceph S3 tests / Basic S3 tests (SQL store) (push) Has been cancelled
test s3 over https using aws-cli / awscli-tests (push) Has been cancelled
TLS Rotation Integration Tests / TLS Rotation Integration Tests (push) Has been cancelled
Vacuum Integration Tests / Vacuum Integration Tests (push) Has been cancelled
S3 Policy Integration Tests / S3 Policy Variables Integration Tests (push) Failing after 3s
S3 Policy Integration Tests / S3 Policy Enforcement Integration Tests (advanced-policy) (push) Failing after 3s
S3 Policy Integration Tests / S3 Policy Enforcement Integration Tests (basic-policy) (push) Failing after 2s
S3 Policy Integration Tests / S3 Policy Enforcement Integration Tests (contextual-policy) (push) Failing after 4s
S3 Policy Integration Tests / Trusted Proxy Detection Tests (push) Failing after 2s
S3 Policy Integration Tests / S3 Policy Shell Integration Tests (push) Failing after 3s
Samba on FUSE Integration / samba-integration (push) Failing after 2s
SFTP Integration Tests / SFTP Integration Testing (push) Failing after 3s
terraform: validate and test modules / fmt, validate, plan-level tests (push) Failing after 3s
terraform: validate and test modules / local cluster smoke test (real weed) (push) Failing after 2s
Volume Server Integration Tests / Volume Server Integration Tests (grpc - Shard 3) (push) Failing after 3s
Volume Server Integration Tests / Volume Server Integration Tests (grpc - Shard 2) (push) Failing after 3s
Volume Server Integration Tests / Volume Server Integration Tests (http - Shard 2) (push) Failing after 2s
Volume Server Integration Tests / Volume Server Integration Tests (http - Shard 3) (push) Failing after 5s
mount: windows conformance / WinFsp conformance (push) Has been cancelled
mount: windows / Mount on Windows (push) Has been cancelled
255 lines
9.4 KiB
Go
255 lines
9.4 KiB
Go
package iceberg
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"testing"
|
|
|
|
"github.com/google/uuid"
|
|
"github.com/seaweedfs/seaweedfs/weed/s3api/s3tables"
|
|
)
|
|
|
|
func TestDecodeCommitTransactionRequest(t *testing.T) {
|
|
body := `{"table-changes":[
|
|
{"identifier":{"namespace":["ns"],"name":"t1"},"requirements":[{"type":"assert-table-uuid","uuid":"00000000-0000-0000-0000-000000000001"}],"updates":[{"action":"set-properties","updates":{"k":"v"}}]},
|
|
{"identifier":{"namespace":["ns","sub"],"name":"t2"},"requirements":[],"updates":[]}
|
|
]}`
|
|
|
|
var req CommitTransactionRequest
|
|
if err := json.Unmarshal([]byte(body), &req); err != nil {
|
|
t.Fatalf("Unmarshal() error = %v", err)
|
|
}
|
|
if len(req.TableChanges) != 2 {
|
|
t.Fatalf("table-changes = %d, want 2", len(req.TableChanges))
|
|
}
|
|
if req.TableChanges[0].Identifier.Name != "t1" {
|
|
t.Fatalf("change[0] name = %q, want t1", req.TableChanges[0].Identifier.Name)
|
|
}
|
|
if got := []string(req.TableChanges[1].Identifier.Namespace); len(got) != 2 || got[1] != "sub" {
|
|
t.Fatalf("change[1] namespace = %v, want [ns sub]", got)
|
|
}
|
|
}
|
|
|
|
func TestParseTableChangeSeparatesStatistics(t *testing.T) {
|
|
change := tableChangeRequest{
|
|
Requirements: json.RawMessage(`[{"type":"assert-table-uuid","uuid":"00000000-0000-0000-0000-000000000001"}]`),
|
|
Updates: []json.RawMessage{
|
|
json.RawMessage(`{"action":"set-properties","updates":{"k":"v"}}`),
|
|
json.RawMessage(`{"action":"set-statistics","snapshot-id":10,"statistics-path":"s3://bucket/table/metadata/stats.puffin","file-size-in-bytes":100,"file-footer-size-in-bytes":20,"blob-metadata":[]}`),
|
|
},
|
|
}
|
|
|
|
requirements, updates, statistics, reqErr := parseTableChange(change)
|
|
if reqErr != nil {
|
|
t.Fatalf("parseTableChange() error = %+v", reqErr)
|
|
}
|
|
if len(requirements) != 1 {
|
|
t.Fatalf("requirements = %d, want 1", len(requirements))
|
|
}
|
|
if len(updates) != 1 {
|
|
t.Fatalf("updates = %d, want 1", len(updates))
|
|
}
|
|
if len(statistics) != 1 || statistics[0].set == nil || statistics[0].set.SnapshotID != 10 {
|
|
t.Fatalf("unexpected statistics updates: %#v", statistics)
|
|
}
|
|
}
|
|
|
|
func TestParseTableChangeRejectsInvalidRequirements(t *testing.T) {
|
|
change := tableChangeRequest{Requirements: json.RawMessage(`{not json}`)}
|
|
_, _, _, reqErr := parseTableChange(change)
|
|
if reqErr == nil {
|
|
t.Fatalf("parseTableChange() expected error for invalid requirements")
|
|
}
|
|
if reqErr.status != 400 {
|
|
t.Fatalf("status = %d, want 400", reqErr.status)
|
|
}
|
|
}
|
|
|
|
func TestCloneTableMetadataIsIndependent(t *testing.T) {
|
|
orig := &s3tables.TableMetadata{
|
|
Iceberg: &s3tables.IcebergMetadata{TableUUID: "uuid-1"},
|
|
FullMetadata: json.RawMessage(`{"v":1}`),
|
|
}
|
|
clone := cloneTableMetadata(orig)
|
|
if clone == orig || clone.Iceberg == orig.Iceberg {
|
|
t.Fatalf("cloneTableMetadata returned aliased pointers")
|
|
}
|
|
|
|
orig.Iceberg.TableUUID = "mutated"
|
|
orig.FullMetadata[0] = 'X'
|
|
if clone.Iceberg.TableUUID != "uuid-1" {
|
|
t.Fatalf("clone Iceberg.TableUUID = %q, want uuid-1", clone.Iceberg.TableUUID)
|
|
}
|
|
if string(clone.FullMetadata) != `{"v":1}` {
|
|
t.Fatalf("clone FullMetadata = %q, mutated with source", clone.FullMetadata)
|
|
}
|
|
|
|
if cloneTableMetadata(nil) != nil {
|
|
t.Fatalf("cloneTableMetadata(nil) = non-nil")
|
|
}
|
|
}
|
|
|
|
func TestBuildTableRestoreRequestCarriesPriorState(t *testing.T) {
|
|
pc := &preparedTableCommit{
|
|
namespace: []string{"ns", "sub"},
|
|
tableName: "t1",
|
|
prevMetadataLoc: "s3://bucket/ns/t1/metadata/v3.metadata.json",
|
|
prevMetadataVersion: 3,
|
|
prevMetadata: &s3tables.TableMetadata{
|
|
Iceberg: &s3tables.IcebergMetadata{TableUUID: uuid.NewString()},
|
|
FullMetadata: json.RawMessage(`{"format-version":2,"last-updated-ms":3}`),
|
|
},
|
|
}
|
|
|
|
req := buildTableRestoreRequest("arn:bucket", pc)
|
|
|
|
if req.TableBucketARN != "arn:bucket" || req.Name != "t1" {
|
|
t.Fatalf("restore target = %s/%s, want arn:bucket/t1", req.TableBucketARN, req.Name)
|
|
}
|
|
if req.MetadataLocation != pc.prevMetadataLoc {
|
|
t.Fatalf("restore MetadataLocation = %q, want %q", req.MetadataLocation, pc.prevMetadataLoc)
|
|
}
|
|
if req.MetadataVersion != pc.prevMetadataVersion {
|
|
t.Fatalf("restore MetadataVersion = %d, want %d", req.MetadataVersion, pc.prevMetadataVersion)
|
|
}
|
|
if req.Metadata == nil || !bytes.Equal(req.Metadata.FullMetadata, pc.prevMetadata.FullMetadata) {
|
|
t.Fatalf("restore FullMetadata = %q, want %q", req.Metadata.FullMetadata, pc.prevMetadata.FullMetadata)
|
|
}
|
|
if req.Metadata.Iceberg == nil || req.Metadata.Iceberg.TableUUID != pc.prevMetadata.Iceberg.TableUUID {
|
|
t.Fatalf("restore TableUUID mismatch")
|
|
}
|
|
}
|
|
|
|
// TestRollbackRevertsEveryFlippedField mirrors handleUpdateTable's partial-field
|
|
// update semantics to prove that after a flip + rollback the table is back to its
|
|
// pre-transaction state. With the old location-only rollback the table kept the
|
|
// new full metadata and version; the restore request must revert all of them.
|
|
func TestRollbackRevertsEveryFlippedField(t *testing.T) {
|
|
// prior on-disk state of an already-committed table.
|
|
prior := tableEntry{
|
|
metadataLocation: "s3://bucket/ns/t1/metadata/v3.metadata.json",
|
|
metadataVersion: 3,
|
|
metadata: &s3tables.TableMetadata{
|
|
Iceberg: &s3tables.IcebergMetadata{TableUUID: "uuid-1"},
|
|
FullMetadata: json.RawMessage(`{"format-version":2,"v":3}`),
|
|
},
|
|
}
|
|
|
|
pc := &preparedTableCommit{
|
|
namespace: []string{"ns"},
|
|
tableName: "t1",
|
|
tableUUID: uuid.MustParse("00000000-0000-0000-0000-000000000001"),
|
|
metadataBytes: json.RawMessage(`{"format-version":2,"v":4}`),
|
|
metadataVersion: 4,
|
|
newMetadataLoc: "s3://bucket/ns/t1/metadata/v4.metadata.json",
|
|
prevMetadataLoc: prior.metadataLocation,
|
|
prevMetadataVersion: prior.metadataVersion,
|
|
prevMetadata: cloneTableMetadata(prior.metadata),
|
|
}
|
|
|
|
// flip: apply the new commit, then roll it back via the restore request.
|
|
state := prior.clone()
|
|
state.applyUpdate(&s3tables.UpdateTableRequest{
|
|
Metadata: &s3tables.TableMetadata{Iceberg: &s3tables.IcebergMetadata{TableUUID: pc.tableUUID.String()}, FullMetadata: pc.metadataBytes},
|
|
MetadataVersion: pc.metadataVersion,
|
|
MetadataLocation: pc.newMetadataLoc,
|
|
})
|
|
state.applyUpdate(buildTableRestoreRequest("arn:bucket", pc))
|
|
|
|
if state.metadataLocation != prior.metadataLocation {
|
|
t.Fatalf("after rollback location = %q, want %q", state.metadataLocation, prior.metadataLocation)
|
|
}
|
|
if state.metadataVersion != prior.metadataVersion {
|
|
t.Fatalf("after rollback version = %d, want %d", state.metadataVersion, prior.metadataVersion)
|
|
}
|
|
if !bytes.Equal(state.metadata.FullMetadata, prior.metadata.FullMetadata) {
|
|
t.Fatalf("after rollback FullMetadata = %q, want %q", state.metadata.FullMetadata, prior.metadata.FullMetadata)
|
|
}
|
|
if state.metadata.Iceberg.TableUUID != prior.metadata.Iceberg.TableUUID {
|
|
t.Fatalf("after rollback TableUUID = %q, want %q", state.metadata.Iceberg.TableUUID, prior.metadata.Iceberg.TableUUID)
|
|
}
|
|
}
|
|
|
|
// TestCleanupSafeMetadataKeepsFailedRollback proves a flip failure at index 2
|
|
// where table 0's rollback also fails leaves table 0's new metadata in place
|
|
// (its pointer still references it), while the rolled-back table 1 and the
|
|
// never-flipped tables 2 and 3 are safe to delete.
|
|
func TestCleanupSafeMetadataKeepsFailedRollback(t *testing.T) {
|
|
prepared := make([]preparedTableCommit, 4)
|
|
rolledBack := []bool{false, true} // tables 0 and 1 were flipped; 0's rollback failed
|
|
|
|
safe := cleanupSafeMetadata(prepared, 2, rolledBack)
|
|
|
|
want := []bool{false, true, true, true}
|
|
for i := range want {
|
|
if safe[i] != want[i] {
|
|
t.Fatalf("safe[%d] = %v, want %v (full %v)", i, safe[i], want[i], safe)
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestCleanupSafeMetadataFirstFlipFails covers a flip failure at the first table:
|
|
// nothing was flipped, so every written file is safe to delete.
|
|
func TestCleanupSafeMetadataFirstFlipFails(t *testing.T) {
|
|
prepared := make([]preparedTableCommit, 3)
|
|
safe := cleanupSafeMetadata(prepared, 0, nil)
|
|
for i, ok := range safe {
|
|
if !ok {
|
|
t.Fatalf("safe[%d] = false, want true (full %v)", i, safe)
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestCleanupSafeMetadataAllRolledBack covers the common case where every prior
|
|
// flip rolls back cleanly, so all metadata files are safe to delete.
|
|
func TestCleanupSafeMetadataAllRolledBack(t *testing.T) {
|
|
prepared := make([]preparedTableCommit, 3)
|
|
safe := cleanupSafeMetadata(prepared, 2, []bool{true, true})
|
|
for i, ok := range safe {
|
|
if !ok {
|
|
t.Fatalf("safe[%d] = false, want true (full %v)", i, safe)
|
|
}
|
|
}
|
|
}
|
|
|
|
// tableEntry models the persisted table fields and the partial-update rules
|
|
// handleUpdateTable applies, so rollback can be exercised without a live filer.
|
|
type tableEntry struct {
|
|
metadataLocation string
|
|
metadataVersion int
|
|
metadata *s3tables.TableMetadata
|
|
}
|
|
|
|
func (e tableEntry) clone() *tableEntry {
|
|
return &tableEntry{
|
|
metadataLocation: e.metadataLocation,
|
|
metadataVersion: e.metadataVersion,
|
|
metadata: cloneTableMetadata(e.metadata),
|
|
}
|
|
}
|
|
|
|
func (e *tableEntry) applyUpdate(req *s3tables.UpdateTableRequest) {
|
|
if req.Metadata != nil {
|
|
if e.metadata == nil {
|
|
e.metadata = &s3tables.TableMetadata{}
|
|
}
|
|
if req.Metadata.Iceberg != nil {
|
|
if e.metadata.Iceberg == nil {
|
|
e.metadata.Iceberg = &s3tables.IcebergMetadata{}
|
|
}
|
|
if req.Metadata.Iceberg.TableUUID != "" {
|
|
e.metadata.Iceberg.TableUUID = req.Metadata.Iceberg.TableUUID
|
|
}
|
|
}
|
|
if len(req.Metadata.FullMetadata) > 0 {
|
|
e.metadata.FullMetadata = req.Metadata.FullMetadata
|
|
}
|
|
}
|
|
if req.MetadataLocation != "" {
|
|
e.metadataLocation = req.MetadataLocation
|
|
}
|
|
if req.MetadataVersion > 0 {
|
|
e.metadataVersion = req.MetadataVersion
|
|
}
|
|
}
|