aboutsummaryrefslogtreecommitdiff
path: root/weed/replication/sink/azuresink/azure_sink_test.go
diff options
context:
space:
mode:
Diffstat (limited to 'weed/replication/sink/azuresink/azure_sink_test.go')
-rw-r--r--weed/replication/sink/azuresink/azure_sink_test.go121
1 files changed, 121 insertions, 0 deletions
diff --git a/weed/replication/sink/azuresink/azure_sink_test.go b/weed/replication/sink/azuresink/azure_sink_test.go
index e139086e6..292e0e95b 100644
--- a/weed/replication/sink/azuresink/azure_sink_test.go
+++ b/weed/replication/sink/azuresink/azure_sink_test.go
@@ -1,6 +1,7 @@
package azuresink
import (
+ "context"
"os"
"testing"
"time"
@@ -320,6 +321,126 @@ func TestAzureSinkPrecondition(t *testing.T) {
sink.DeleteEntry(testKey, false, false, nil)
}
+// Helper function to get blob content length with timeout
+func getBlobContentLength(t *testing.T, sink *AzureSink, key string) int64 {
+ t.Helper()
+ containerClient := sink.client.ServiceClient().NewContainerClient(sink.container)
+ blobClient := containerClient.NewAppendBlobClient(cleanKey(key))
+
+ ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
+ defer cancel()
+
+ props, err := blobClient.GetProperties(ctx, nil)
+ if err != nil {
+ t.Fatalf("Failed to get blob properties: %v", err)
+ }
+ if props.ContentLength == nil {
+ return 0
+ }
+ return *props.ContentLength
+}
+
+// Test that repeated creates don't result in zero-byte files (regression test for critical bug)
+func TestAzureSinkIdempotentCreate(t *testing.T) {
+ accountName := os.Getenv("AZURE_STORAGE_ACCOUNT")
+ accountKey := os.Getenv("AZURE_STORAGE_ACCESS_KEY")
+ testContainer := os.Getenv("AZURE_TEST_CONTAINER")
+
+ if accountName == "" || accountKey == "" {
+ t.Skip("Skipping Azure sink idempotent create test: credentials not set")
+ }
+ if testContainer == "" {
+ testContainer = "seaweedfs-test"
+ }
+
+ sink := &AzureSink{}
+ err := sink.initialize(accountName, accountKey, testContainer, "/test")
+ if err != nil {
+ t.Fatalf("Failed to initialize: %v", err)
+ }
+
+ testKey := "/test-idempotent-" + time.Now().Format("20060102-150405") + ".txt"
+ testContent := []byte("This is test content that should never be empty!")
+
+ // Use fixed time reference for deterministic behavior
+ testTime := time.Now()
+
+ // Clean up at the end
+ defer sink.DeleteEntry(testKey, false, false, nil)
+
+ // Test 1: Create a file with content
+ t.Run("FirstCreate", func(t *testing.T) {
+ entry := &filer_pb.Entry{
+ Content: testContent,
+ Attributes: &filer_pb.FuseAttributes{
+ Mtime: testTime.Unix(),
+ },
+ }
+ err := sink.CreateEntry(testKey, entry, nil)
+ if err != nil {
+ t.Fatalf("Failed to create entry: %v", err)
+ }
+
+ // Verify the file has content (not zero bytes)
+ contentLength := getBlobContentLength(t, sink, testKey)
+ if contentLength == 0 {
+ t.Errorf("File has zero bytes after creation! Expected %d bytes", len(testContent))
+ } else if contentLength != int64(len(testContent)) {
+ t.Errorf("File size mismatch: expected %d, got %d", len(testContent), contentLength)
+ } else {
+ t.Logf("File created with correct size: %d bytes", contentLength)
+ }
+ })
+
+ // Test 2: Create the same file again (idempotent operation - simulates replication running multiple times)
+ // This is where the zero-byte bug occurred: blob existed, precondition failed, returned early without writing data
+ t.Run("IdempotentCreate", func(t *testing.T) {
+ entry := &filer_pb.Entry{
+ Content: testContent,
+ Attributes: &filer_pb.FuseAttributes{
+ Mtime: testTime.Add(1 * time.Second).Unix(), // Slightly newer mtime
+ },
+ }
+ err := sink.CreateEntry(testKey, entry, nil)
+ if err != nil {
+ t.Fatalf("Failed on idempotent create: %v", err)
+ }
+
+ // CRITICAL: Verify the file STILL has content (not zero bytes)
+ contentLength := getBlobContentLength(t, sink, testKey)
+ if contentLength == 0 {
+ t.Errorf("ZERO-BYTE BUG: File became empty after idempotent create! Expected %d bytes", len(testContent))
+ } else if contentLength < int64(len(testContent)) {
+ t.Errorf("File lost content: expected at least %d bytes, got %d", len(testContent), contentLength)
+ } else {
+ t.Logf("File still has content after idempotent create: %d bytes", contentLength)
+ }
+ })
+
+ // Test 3: Try creating with older mtime (should skip but not leave zero bytes)
+ t.Run("CreateWithOlderMtime", func(t *testing.T) {
+ entry := &filer_pb.Entry{
+ Content: []byte("This content should be skipped"),
+ Attributes: &filer_pb.FuseAttributes{
+ Mtime: testTime.Add(-1 * time.Hour).Unix(), // Older timestamp
+ },
+ }
+ err := sink.CreateEntry(testKey, entry, nil)
+ // Should succeed by skipping (no error expected)
+ if err != nil {
+ t.Fatalf("Create with older mtime should be skipped and return no error, but got: %v", err)
+ }
+
+ // Verify file STILL has content
+ contentLength := getBlobContentLength(t, sink, testKey)
+ if contentLength == 0 {
+ t.Errorf("File became empty after create with older mtime!")
+ } else {
+ t.Logf("File preserved content despite older mtime: %d bytes", contentLength)
+ }
+ })
+}
+
// Benchmark tests
func BenchmarkCleanKey(b *testing.B) {
keys := []string{