You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							643 lines
						
					
					
						
							20 KiB
						
					
					
				
			
		
		
		
			
			
			
		
		
	
	
							643 lines
						
					
					
						
							20 KiB
						
					
					
				| package storage | |
| 
 | |
| import ( | |
| 	"os" | |
| 	"path/filepath" | |
| 	"testing" | |
| 
 | |
| 	"github.com/seaweedfs/seaweedfs/weed/storage/erasure_coding" | |
| 	"github.com/seaweedfs/seaweedfs/weed/storage/needle" | |
| 	"github.com/seaweedfs/seaweedfs/weed/storage/types" | |
| 	"github.com/seaweedfs/seaweedfs/weed/util" | |
| ) | |
| 
 | |
| // TestIncompleteEcEncodingCleanup tests the cleanup logic for incomplete EC encoding scenarios | |
| func TestIncompleteEcEncodingCleanup(t *testing.T) { | |
| 	tests := []struct { | |
| 		name              string | |
| 		volumeId          needle.VolumeId | |
| 		collection        string | |
| 		createDatFile     bool | |
| 		createEcxFile     bool | |
| 		createEcjFile     bool | |
| 		numShards         int | |
| 		expectCleanup     bool | |
| 		expectLoadSuccess bool | |
| 	}{ | |
| 		{ | |
| 			name:              "Incomplete EC: shards without .ecx, .dat exists - should cleanup", | |
| 			volumeId:          100, | |
| 			collection:        "", | |
| 			createDatFile:     true, | |
| 			createEcxFile:     false, | |
| 			createEcjFile:     false, | |
| 			numShards:         14, // All shards but no .ecx | |
| 			expectCleanup:     true, | |
| 			expectLoadSuccess: false, | |
| 		}, | |
| 		{ | |
| 			name:              "Distributed EC: shards without .ecx, .dat deleted - should NOT cleanup", | |
| 			volumeId:          101, | |
| 			collection:        "", | |
| 			createDatFile:     false, | |
| 			createEcxFile:     false, | |
| 			createEcjFile:     false, | |
| 			numShards:         5, // Partial shards, distributed | |
| 			expectCleanup:     false, | |
| 			expectLoadSuccess: false, | |
| 		}, | |
| 		{ | |
| 			name:              "Incomplete EC: shards with .ecx but < 10 shards, .dat exists - should cleanup", | |
| 			volumeId:          102, | |
| 			collection:        "", | |
| 			createDatFile:     true, | |
| 			createEcxFile:     true, | |
| 			createEcjFile:     false, | |
| 			numShards:         7, // Less than DataShardsCount (10) | |
| 			expectCleanup:     true, | |
| 			expectLoadSuccess: false, | |
| 		}, | |
| 		{ | |
| 			name:              "Valid local EC: shards with .ecx, >= 10 shards, .dat exists - should load", | |
| 			volumeId:          103, | |
| 			collection:        "", | |
| 			createDatFile:     true, | |
| 			createEcxFile:     true, | |
| 			createEcjFile:     false, | |
| 			numShards:         14, // All shards | |
| 			expectCleanup:     false, | |
| 			expectLoadSuccess: true, // Would succeed if .ecx was valid | |
| 		}, | |
| 		{ | |
| 			name:              "Distributed EC: shards with .ecx, .dat deleted - should load", | |
| 			volumeId:          104, | |
| 			collection:        "", | |
| 			createDatFile:     false, | |
| 			createEcxFile:     true, | |
| 			createEcjFile:     false, | |
| 			numShards:         10, // Enough shards | |
| 			expectCleanup:     false, | |
| 			expectLoadSuccess: true, // Would succeed if .ecx was valid | |
| 		}, | |
| 		{ | |
| 			name:              "Incomplete EC with collection: shards without .ecx, .dat exists - should cleanup", | |
| 			volumeId:          105, | |
| 			collection:        "test_collection", | |
| 			createDatFile:     true, | |
| 			createEcxFile:     false, | |
| 			createEcjFile:     false, | |
| 			numShards:         14, | |
| 			expectCleanup:     true, | |
| 			expectLoadSuccess: false, | |
| 		}, | |
| 	} | |
| 
 | |
| 	for _, tt := range tests { | |
| 		t.Run(tt.name, func(t *testing.T) { | |
| 			// Use per-subtest temp directory for stronger isolation | |
| 			tempDir := t.TempDir() | |
| 
 | |
| 			// Create DiskLocation | |
| 			minFreeSpace := util.MinFreeSpace{Type: util.AsPercent, Percent: 1, Raw: "1"} | |
| 			diskLocation := &DiskLocation{ | |
| 				Directory:              tempDir, | |
| 				DirectoryUuid:          "test-uuid", | |
| 				IdxDirectory:           tempDir, | |
| 				DiskType:               types.HddType, | |
| 				MaxVolumeCount:         100, | |
| 				OriginalMaxVolumeCount: 100, | |
| 				MinFreeSpace:           minFreeSpace, | |
| 			} | |
| 			diskLocation.volumes = make(map[needle.VolumeId]*Volume) | |
| 			diskLocation.ecVolumes = make(map[needle.VolumeId]*erasure_coding.EcVolume) | |
| 
 | |
| 			// Setup test files | |
| 			baseFileName := erasure_coding.EcShardFileName(tt.collection, tempDir, int(tt.volumeId)) | |
| 
 | |
| 			// Use deterministic but small size: 10MB .dat => 1MB per shard | |
| 			datFileSize := int64(10 * 1024 * 1024) // 10MB | |
| 			expectedShardSize := calculateExpectedShardSize(datFileSize) | |
| 
 | |
| 			// Create .dat file if needed | |
| 			if tt.createDatFile { | |
| 				datFile, err := os.Create(baseFileName + ".dat") | |
| 				if err != nil { | |
| 					t.Fatalf("Failed to create .dat file: %v", err) | |
| 				} | |
| 				if err := datFile.Truncate(datFileSize); err != nil { | |
| 					t.Fatalf("Failed to truncate .dat file: %v", err) | |
| 				} | |
| 				if err := datFile.Close(); err != nil { | |
| 					t.Fatalf("Failed to close .dat file: %v", err) | |
| 				} | |
| 			} | |
| 
 | |
| 			// Create EC shard files | |
| 			for i := 0; i < tt.numShards; i++ { | |
| 				shardFile, err := os.Create(baseFileName + erasure_coding.ToExt(i)) | |
| 				if err != nil { | |
| 					t.Fatalf("Failed to create shard file: %v", err) | |
| 				} | |
| 				if err := shardFile.Truncate(expectedShardSize); err != nil { | |
| 					t.Fatalf("Failed to truncate shard file: %v", err) | |
| 				} | |
| 				if err := shardFile.Close(); err != nil { | |
| 					t.Fatalf("Failed to close shard file: %v", err) | |
| 				} | |
| 			} | |
| 
 | |
| 			// Create .ecx file if needed | |
| 			if tt.createEcxFile { | |
| 				ecxFile, err := os.Create(baseFileName + ".ecx") | |
| 				if err != nil { | |
| 					t.Fatalf("Failed to create .ecx file: %v", err) | |
| 				} | |
| 				if _, err := ecxFile.WriteString("dummy ecx data"); err != nil { | |
| 					ecxFile.Close() | |
| 					t.Fatalf("Failed to write .ecx file: %v", err) | |
| 				} | |
| 				if err := ecxFile.Close(); err != nil { | |
| 					t.Fatalf("Failed to close .ecx file: %v", err) | |
| 				} | |
| 			} | |
| 
 | |
| 			// Create .ecj file if needed | |
| 			if tt.createEcjFile { | |
| 				ecjFile, err := os.Create(baseFileName + ".ecj") | |
| 				if err != nil { | |
| 					t.Fatalf("Failed to create .ecj file: %v", err) | |
| 				} | |
| 				if _, err := ecjFile.WriteString("dummy ecj data"); err != nil { | |
| 					ecjFile.Close() | |
| 					t.Fatalf("Failed to write .ecj file: %v", err) | |
| 				} | |
| 				if err := ecjFile.Close(); err != nil { | |
| 					t.Fatalf("Failed to close .ecj file: %v", err) | |
| 				} | |
| 			} | |
| 
 | |
| 			// Run loadAllEcShards | |
| 			loadErr := diskLocation.loadAllEcShards() | |
| 			if loadErr != nil { | |
| 				t.Logf("loadAllEcShards returned error (expected in some cases): %v", loadErr) | |
| 			} | |
| 
 | |
| 			// Test idempotency - running again should not cause issues | |
| 			loadErr2 := diskLocation.loadAllEcShards() | |
| 			if loadErr2 != nil { | |
| 				t.Logf("Second loadAllEcShards returned error: %v", loadErr2) | |
| 			} | |
| 
 | |
| 			// Verify cleanup expectations | |
| 			if tt.expectCleanup { | |
| 				// Check that files were cleaned up | |
| 				if util.FileExists(baseFileName + ".ecx") { | |
| 					t.Errorf("Expected .ecx to be cleaned up but it still exists") | |
| 				} | |
| 				if util.FileExists(baseFileName + ".ecj") { | |
| 					t.Errorf("Expected .ecj to be cleaned up but it still exists") | |
| 				} | |
| 				for i := 0; i < erasure_coding.TotalShardsCount; i++ { | |
| 					shardFile := baseFileName + erasure_coding.ToExt(i) | |
| 					if util.FileExists(shardFile) { | |
| 						t.Errorf("Expected shard %d to be cleaned up but it still exists", i) | |
| 					} | |
| 				} | |
| 				// .dat file should still exist (not cleaned up) | |
| 				if tt.createDatFile && !util.FileExists(baseFileName+".dat") { | |
| 					t.Errorf("Expected .dat file to remain but it was deleted") | |
| 				} | |
| 			} else { | |
| 				// Check that files were NOT cleaned up | |
| 				for i := 0; i < tt.numShards; i++ { | |
| 					shardFile := baseFileName + erasure_coding.ToExt(i) | |
| 					if !util.FileExists(shardFile) { | |
| 						t.Errorf("Expected shard %d to remain but it was cleaned up", i) | |
| 					} | |
| 				} | |
| 				if tt.createEcxFile && !util.FileExists(baseFileName+".ecx") { | |
| 					t.Errorf("Expected .ecx to remain but it was cleaned up") | |
| 				} | |
| 			} | |
| 
 | |
| 			// Verify load expectations | |
| 			if tt.expectLoadSuccess { | |
| 				if diskLocation.EcShardCount() == 0 { | |
| 					t.Errorf("Expected EC shards to be loaded for volume %d", tt.volumeId) | |
| 				} | |
| 			} | |
| 
 | |
| 		}) | |
| 	} | |
| } | |
| 
 | |
| // TestValidateEcVolume tests the validateEcVolume function | |
| func TestValidateEcVolume(t *testing.T) { | |
| 	tempDir := t.TempDir() | |
| 
 | |
| 	minFreeSpace := util.MinFreeSpace{Type: util.AsPercent, Percent: 1, Raw: "1"} | |
| 	diskLocation := &DiskLocation{ | |
| 		Directory:     tempDir, | |
| 		DirectoryUuid: "test-uuid", | |
| 		IdxDirectory:  tempDir, | |
| 		DiskType:      types.HddType, | |
| 		MinFreeSpace:  minFreeSpace, | |
| 	} | |
| 
 | |
| 	tests := []struct { | |
| 		name          string | |
| 		volumeId      needle.VolumeId | |
| 		collection    string | |
| 		createDatFile bool | |
| 		numShards     int | |
| 		expectValid   bool | |
| 	}{ | |
| 		{ | |
| 			name:          "Valid: .dat exists with 10+ shards", | |
| 			volumeId:      200, | |
| 			collection:    "", | |
| 			createDatFile: true, | |
| 			numShards:     10, | |
| 			expectValid:   true, | |
| 		}, | |
| 		{ | |
| 			name:          "Invalid: .dat exists with < 10 shards", | |
| 			volumeId:      201, | |
| 			collection:    "", | |
| 			createDatFile: true, | |
| 			numShards:     9, | |
| 			expectValid:   false, | |
| 		}, | |
| 		{ | |
| 			name:          "Valid: .dat deleted (distributed EC) with any shards", | |
| 			volumeId:      202, | |
| 			collection:    "", | |
| 			createDatFile: false, | |
| 			numShards:     5, | |
| 			expectValid:   true, | |
| 		}, | |
| 		{ | |
| 			name:          "Valid: .dat deleted (distributed EC) with no shards", | |
| 			volumeId:      203, | |
| 			collection:    "", | |
| 			createDatFile: false, | |
| 			numShards:     0, | |
| 			expectValid:   true, | |
| 		}, | |
| 		{ | |
| 			name:          "Invalid: zero-byte shard files should not count", | |
| 			volumeId:      204, | |
| 			collection:    "", | |
| 			createDatFile: true, | |
| 			numShards:     0, // Will create 10 zero-byte files below | |
| 			expectValid:   false, | |
| 		}, | |
| 		{ | |
| 			name:          "Invalid: .dat exists with different size shards", | |
| 			volumeId:      205, | |
| 			collection:    "", | |
| 			createDatFile: true, | |
| 			numShards:     10, // Will create shards with varying sizes | |
| 			expectValid:   false, | |
| 		}, | |
| 	} | |
| 
 | |
| 	for _, tt := range tests { | |
| 		t.Run(tt.name, func(t *testing.T) { | |
| 			baseFileName := erasure_coding.EcShardFileName(tt.collection, tempDir, int(tt.volumeId)) | |
| 
 | |
| 			// For proper testing, we need to use realistic sizes that match EC encoding | |
| 			// EC uses large blocks (1GB) and small blocks (1MB) | |
| 			// For test purposes, use a small .dat file size that still exercises the logic | |
| 			// 10MB .dat file = 1MB per shard (one small batch, fast and deterministic) | |
| 			datFileSize := int64(10 * 1024 * 1024) // 10MB | |
| 			expectedShardSize := calculateExpectedShardSize(datFileSize) | |
| 
 | |
| 			// Create .dat file if needed | |
| 			if tt.createDatFile { | |
| 				datFile, err := os.Create(baseFileName + ".dat") | |
| 				if err != nil { | |
| 					t.Fatalf("Failed to create .dat file: %v", err) | |
| 				} | |
| 				// Write minimal data (don't need to fill entire 10GB for tests) | |
| 				datFile.Truncate(datFileSize) | |
| 				datFile.Close() | |
| 			} | |
| 
 | |
| 			// Create EC shard files with correct size | |
| 			for i := 0; i < tt.numShards; i++ { | |
| 				shardFile, err := os.Create(baseFileName + erasure_coding.ToExt(i)) | |
| 				if err != nil { | |
| 					t.Fatalf("Failed to create shard file: %v", err) | |
| 				} | |
| 				// Use truncate to create file of correct size without allocating all the space | |
| 				if err := shardFile.Truncate(expectedShardSize); err != nil { | |
| 					shardFile.Close() | |
| 					t.Fatalf("Failed to truncate shard file: %v", err) | |
| 				} | |
| 				if err := shardFile.Close(); err != nil { | |
| 					t.Fatalf("Failed to close shard file: %v", err) | |
| 				} | |
| 			} | |
| 
 | |
| 			// For zero-byte test case, create empty files for all data shards | |
| 			if tt.volumeId == 204 { | |
| 				for i := 0; i < erasure_coding.DataShardsCount; i++ { | |
| 					shardFile, err := os.Create(baseFileName + erasure_coding.ToExt(i)) | |
| 					if err != nil { | |
| 						t.Fatalf("Failed to create empty shard file: %v", err) | |
| 					} | |
| 					// Don't write anything - leave as zero-byte | |
| 					shardFile.Close() | |
| 				} | |
| 			} | |
| 
 | |
| 			// For mismatched shard size test case, create shards with different sizes | |
| 			if tt.volumeId == 205 { | |
| 				for i := 0; i < erasure_coding.DataShardsCount; i++ { | |
| 					shardFile, err := os.Create(baseFileName + erasure_coding.ToExt(i)) | |
| 					if err != nil { | |
| 						t.Fatalf("Failed to create shard file: %v", err) | |
| 					} | |
| 					// Write different amount of data to each shard | |
| 					data := make([]byte, 100+i*10) | |
| 					shardFile.Write(data) | |
| 					shardFile.Close() | |
| 				} | |
| 			} | |
| 
 | |
| 			// Test validation | |
| 			isValid := diskLocation.validateEcVolume(tt.collection, tt.volumeId) | |
| 			if isValid != tt.expectValid { | |
| 				t.Errorf("Expected validation result %v but got %v", tt.expectValid, isValid) | |
| 			} | |
| 		}) | |
| 	} | |
| } | |
| 
 | |
| // TestRemoveEcVolumeFiles tests the removeEcVolumeFiles function | |
| func TestRemoveEcVolumeFiles(t *testing.T) { | |
| 	tests := []struct { | |
| 		name           string | |
| 		separateIdxDir bool | |
| 	}{ | |
| 		{"Same directory for data and index", false}, | |
| 		{"Separate idx directory", true}, | |
| 	} | |
| 
 | |
| 	for _, tt := range tests { | |
| 		t.Run(tt.name, func(t *testing.T) { | |
| 			tempDir := t.TempDir() | |
| 
 | |
| 			var dataDir, idxDir string | |
| 			if tt.separateIdxDir { | |
| 				dataDir = filepath.Join(tempDir, "data") | |
| 				idxDir = filepath.Join(tempDir, "idx") | |
| 				os.MkdirAll(dataDir, 0755) | |
| 				os.MkdirAll(idxDir, 0755) | |
| 			} else { | |
| 				dataDir = tempDir | |
| 				idxDir = tempDir | |
| 			} | |
| 
 | |
| 			minFreeSpace := util.MinFreeSpace{Type: util.AsPercent, Percent: 1, Raw: "1"} | |
| 			diskLocation := &DiskLocation{ | |
| 				Directory:     dataDir, | |
| 				DirectoryUuid: "test-uuid", | |
| 				IdxDirectory:  idxDir, | |
| 				DiskType:      types.HddType, | |
| 				MinFreeSpace:  minFreeSpace, | |
| 			} | |
| 
 | |
| 			volumeId := needle.VolumeId(300) | |
| 			collection := "" | |
| 			dataBaseFileName := erasure_coding.EcShardFileName(collection, dataDir, int(volumeId)) | |
| 			idxBaseFileName := erasure_coding.EcShardFileName(collection, idxDir, int(volumeId)) | |
| 
 | |
| 			// Create all EC shard files in data directory | |
| 			for i := 0; i < erasure_coding.TotalShardsCount; i++ { | |
| 				shardFile, err := os.Create(dataBaseFileName + erasure_coding.ToExt(i)) | |
| 				if err != nil { | |
| 					t.Fatalf("Failed to create shard file: %v", err) | |
| 				} | |
| 				if _, err := shardFile.WriteString("dummy shard data"); err != nil { | |
| 					shardFile.Close() | |
| 					t.Fatalf("Failed to write shard file: %v", err) | |
| 				} | |
| 				if err := shardFile.Close(); err != nil { | |
| 					t.Fatalf("Failed to close shard file: %v", err) | |
| 				} | |
| 			} | |
| 
 | |
| 			// Create .ecx file in idx directory | |
| 			ecxFile, err := os.Create(idxBaseFileName + ".ecx") | |
| 			if err != nil { | |
| 				t.Fatalf("Failed to create .ecx file: %v", err) | |
| 			} | |
| 			if _, err := ecxFile.WriteString("dummy ecx data"); err != nil { | |
| 				ecxFile.Close() | |
| 				t.Fatalf("Failed to write .ecx file: %v", err) | |
| 			} | |
| 			if err := ecxFile.Close(); err != nil { | |
| 				t.Fatalf("Failed to close .ecx file: %v", err) | |
| 			} | |
| 
 | |
| 			// Create .ecj file in idx directory | |
| 			ecjFile, err := os.Create(idxBaseFileName + ".ecj") | |
| 			if err != nil { | |
| 				t.Fatalf("Failed to create .ecj file: %v", err) | |
| 			} | |
| 			if _, err := ecjFile.WriteString("dummy ecj data"); err != nil { | |
| 				ecjFile.Close() | |
| 				t.Fatalf("Failed to write .ecj file: %v", err) | |
| 			} | |
| 			if err := ecjFile.Close(); err != nil { | |
| 				t.Fatalf("Failed to close .ecj file: %v", err) | |
| 			} | |
| 
 | |
| 			// Create .dat file in data directory (should NOT be removed) | |
| 			datFile, err := os.Create(dataBaseFileName + ".dat") | |
| 			if err != nil { | |
| 				t.Fatalf("Failed to create .dat file: %v", err) | |
| 			} | |
| 			if _, err := datFile.WriteString("dummy dat data"); err != nil { | |
| 				datFile.Close() | |
| 				t.Fatalf("Failed to write .dat file: %v", err) | |
| 			} | |
| 			if err := datFile.Close(); err != nil { | |
| 				t.Fatalf("Failed to close .dat file: %v", err) | |
| 			} | |
| 
 | |
| 			// Call removeEcVolumeFiles | |
| 			diskLocation.removeEcVolumeFiles(collection, volumeId) | |
| 
 | |
| 			// Verify all EC shard files are removed from data directory | |
| 			for i := 0; i < erasure_coding.TotalShardsCount; i++ { | |
| 				shardFile := dataBaseFileName + erasure_coding.ToExt(i) | |
| 				if util.FileExists(shardFile) { | |
| 					t.Errorf("Shard file %d should be removed but still exists", i) | |
| 				} | |
| 			} | |
| 
 | |
| 			// Verify .ecx file is removed from idx directory | |
| 			if util.FileExists(idxBaseFileName + ".ecx") { | |
| 				t.Errorf(".ecx file should be removed but still exists") | |
| 			} | |
| 
 | |
| 			// Verify .ecj file is removed from idx directory | |
| 			if util.FileExists(idxBaseFileName + ".ecj") { | |
| 				t.Errorf(".ecj file should be removed but still exists") | |
| 			} | |
| 
 | |
| 			// Verify .dat file is NOT removed from data directory | |
| 			if !util.FileExists(dataBaseFileName + ".dat") { | |
| 				t.Errorf(".dat file should NOT be removed but was deleted") | |
| 			} | |
| 		}) | |
| 	} | |
| } | |
| 
 | |
| // TestEcCleanupWithSeparateIdxDirectory tests EC cleanup when idx directory is different | |
| func TestEcCleanupWithSeparateIdxDirectory(t *testing.T) { | |
| 	tempDir := t.TempDir() | |
| 
 | |
| 	idxDir := filepath.Join(tempDir, "idx") | |
| 	dataDir := filepath.Join(tempDir, "data") | |
| 	os.MkdirAll(idxDir, 0755) | |
| 	os.MkdirAll(dataDir, 0755) | |
| 
 | |
| 	minFreeSpace := util.MinFreeSpace{Type: util.AsPercent, Percent: 1, Raw: "1"} | |
| 	diskLocation := &DiskLocation{ | |
| 		Directory:     dataDir, | |
| 		DirectoryUuid: "test-uuid", | |
| 		IdxDirectory:  idxDir, | |
| 		DiskType:      types.HddType, | |
| 		MinFreeSpace:  minFreeSpace, | |
| 	} | |
| 	diskLocation.volumes = make(map[needle.VolumeId]*Volume) | |
| 	diskLocation.ecVolumes = make(map[needle.VolumeId]*erasure_coding.EcVolume) | |
| 
 | |
| 	volumeId := needle.VolumeId(400) | |
| 	collection := "" | |
| 
 | |
| 	// Create shards in data directory (shards only go to Directory, not IdxDirectory) | |
| 	dataBaseFileName := erasure_coding.EcShardFileName(collection, dataDir, int(volumeId)) | |
| 	for i := 0; i < erasure_coding.TotalShardsCount; i++ { | |
| 		shardFile, err := os.Create(dataBaseFileName + erasure_coding.ToExt(i)) | |
| 		if err != nil { | |
| 			t.Fatalf("Failed to create shard file: %v", err) | |
| 		} | |
| 		if _, err := shardFile.WriteString("dummy shard data"); err != nil { | |
| 			t.Fatalf("Failed to write shard file: %v", err) | |
| 		} | |
| 		if err := shardFile.Close(); err != nil { | |
| 			t.Fatalf("Failed to close shard file: %v", err) | |
| 		} | |
| 	} | |
| 
 | |
| 	// Create .dat in data directory | |
| 	datFile, err := os.Create(dataBaseFileName + ".dat") | |
| 	if err != nil { | |
| 		t.Fatalf("Failed to create .dat file: %v", err) | |
| 	} | |
| 	if _, err := datFile.WriteString("dummy data"); err != nil { | |
| 		t.Fatalf("Failed to write .dat file: %v", err) | |
| 	} | |
| 	if err := datFile.Close(); err != nil { | |
| 		t.Fatalf("Failed to close .dat file: %v", err) | |
| 	} | |
| 
 | |
| 	// Do not create .ecx: trigger orphaned-shards cleanup when .dat exists | |
|  | |
| 	// Run loadAllEcShards | |
| 	loadErr := diskLocation.loadAllEcShards() | |
| 	if loadErr != nil { | |
| 		t.Logf("loadAllEcShards error: %v", loadErr) | |
| 	} | |
| 
 | |
| 	// Verify cleanup occurred in data directory (shards) | |
| 	for i := 0; i < erasure_coding.TotalShardsCount; i++ { | |
| 		shardFile := dataBaseFileName + erasure_coding.ToExt(i) | |
| 		if util.FileExists(shardFile) { | |
| 			t.Errorf("Shard file %d should be cleaned up but still exists", i) | |
| 		} | |
| 	} | |
| 
 | |
| 	// Verify .dat in data directory still exists (only EC files are cleaned up) | |
| 	if !util.FileExists(dataBaseFileName + ".dat") { | |
| 		t.Errorf(".dat file should remain but was deleted") | |
| 	} | |
| } | |
| 
 | |
| // TestDistributedEcVolumeNoFileDeletion verifies that distributed EC volumes | |
| // (where .dat is deleted) do NOT have their shard files deleted when load fails | |
| // This tests the critical bug fix where DestroyEcVolume was incorrectly deleting files | |
| func TestDistributedEcVolumeNoFileDeletion(t *testing.T) { | |
| 	tempDir := t.TempDir() | |
| 
 | |
| 	minFreeSpace := util.MinFreeSpace{Type: util.AsPercent, Percent: 1, Raw: "1"} | |
| 	diskLocation := &DiskLocation{ | |
| 		Directory:     tempDir, | |
| 		DirectoryUuid: "test-uuid", | |
| 		IdxDirectory:  tempDir, | |
| 		DiskType:      types.HddType, | |
| 		MinFreeSpace:  minFreeSpace, | |
| 		ecVolumes:     make(map[needle.VolumeId]*erasure_coding.EcVolume), | |
| 	} | |
| 
 | |
| 	collection := "" | |
| 	volumeId := needle.VolumeId(500) | |
| 	baseFileName := erasure_coding.EcShardFileName(collection, tempDir, int(volumeId)) | |
| 
 | |
| 	// Create EC shards (only 5 shards - less than DataShardsCount, but OK for distributed EC) | |
| 	numDistributedShards := 5 | |
| 	for i := 0; i < numDistributedShards; i++ { | |
| 		shardFile, err := os.Create(baseFileName + erasure_coding.ToExt(i)) | |
| 		if err != nil { | |
| 			t.Fatalf("Failed to create shard file: %v", err) | |
| 		} | |
| 		if _, err := shardFile.WriteString("dummy shard data"); err != nil { | |
| 			shardFile.Close() | |
| 			t.Fatalf("Failed to write shard file: %v", err) | |
| 		} | |
| 		if err := shardFile.Close(); err != nil { | |
| 			t.Fatalf("Failed to close shard file: %v", err) | |
| 		} | |
| 	} | |
| 
 | |
| 	// Create .ecx file to trigger EC loading | |
| 	ecxFile, err := os.Create(baseFileName + ".ecx") | |
| 	if err != nil { | |
| 		t.Fatalf("Failed to create .ecx file: %v", err) | |
| 	} | |
| 	if _, err := ecxFile.WriteString("dummy ecx data"); err != nil { | |
| 		ecxFile.Close() | |
| 		t.Fatalf("Failed to write .ecx file: %v", err) | |
| 	} | |
| 	if err := ecxFile.Close(); err != nil { | |
| 		t.Fatalf("Failed to close .ecx file: %v", err) | |
| 	} | |
| 
 | |
| 	// NO .dat file - this is a distributed EC volume | |
|  | |
| 	// Run loadAllEcShards - this should fail but NOT delete shard files | |
| 	loadErr := diskLocation.loadAllEcShards() | |
| 	if loadErr != nil { | |
| 		t.Logf("loadAllEcShards returned error (expected): %v", loadErr) | |
| 	} | |
| 
 | |
| 	// CRITICAL CHECK: Verify shard files still exist (should NOT be deleted) | |
| 	for i := 0; i < 5; i++ { | |
| 		shardFile := baseFileName + erasure_coding.ToExt(i) | |
| 		if !util.FileExists(shardFile) { | |
| 			t.Errorf("CRITICAL BUG: Shard file %s was deleted for distributed EC volume!", shardFile) | |
| 		} | |
| 	} | |
| 
 | |
| 	// Verify .ecx file still exists (should NOT be deleted for distributed EC) | |
| 	if !util.FileExists(baseFileName + ".ecx") { | |
| 		t.Errorf("CRITICAL BUG: .ecx file was deleted for distributed EC volume!") | |
| 	} | |
| 
 | |
| 	t.Logf("SUCCESS: Distributed EC volume files preserved (not deleted)") | |
| }
 |