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.
		
		
		
		
		
			
		
			
				
					
					
						
							215 lines
						
					
					
						
							6.3 KiB
						
					
					
				
			
		
		
		
			
			
			
		
		
	
	
							215 lines
						
					
					
						
							6.3 KiB
						
					
					
				| package topology | |
| 
 | |
| import ( | |
| 	"sync" | |
| 	"testing" | |
| 	"time" | |
| 
 | |
| 	"github.com/seaweedfs/seaweedfs/weed/storage/types" | |
| ) | |
| 
 | |
| func TestCapacityReservations_BasicOperations(t *testing.T) { | |
| 	cr := newCapacityReservations() | |
| 	diskType := types.HardDriveType | |
| 
 | |
| 	// Test initial state | |
| 	if count := cr.getReservedCount(diskType); count != 0 { | |
| 		t.Errorf("Expected 0 reserved count initially, got %d", count) | |
| 	} | |
| 
 | |
| 	// Test add reservation | |
| 	reservationId := cr.addReservation(diskType, 5) | |
| 	if reservationId == "" { | |
| 		t.Error("Expected non-empty reservation ID") | |
| 	} | |
| 
 | |
| 	if count := cr.getReservedCount(diskType); count != 5 { | |
| 		t.Errorf("Expected 5 reserved count, got %d", count) | |
| 	} | |
| 
 | |
| 	// Test multiple reservations | |
| 	cr.addReservation(diskType, 3) | |
| 	if count := cr.getReservedCount(diskType); count != 8 { | |
| 		t.Errorf("Expected 8 reserved count after second reservation, got %d", count) | |
| 	} | |
| 
 | |
| 	// Test remove reservation | |
| 	success := cr.removeReservation(reservationId) | |
| 	if !success { | |
| 		t.Error("Expected successful removal of existing reservation") | |
| 	} | |
| 
 | |
| 	if count := cr.getReservedCount(diskType); count != 3 { | |
| 		t.Errorf("Expected 3 reserved count after removal, got %d", count) | |
| 	} | |
| 
 | |
| 	// Test remove non-existent reservation | |
| 	success = cr.removeReservation("non-existent-id") | |
| 	if success { | |
| 		t.Error("Expected failure when removing non-existent reservation") | |
| 	} | |
| } | |
| 
 | |
| func TestCapacityReservations_ExpiredCleaning(t *testing.T) { | |
| 	cr := newCapacityReservations() | |
| 	diskType := types.HardDriveType | |
| 
 | |
| 	// Add reservations and manipulate their creation time | |
| 	reservationId1 := cr.addReservation(diskType, 3) | |
| 	reservationId2 := cr.addReservation(diskType, 2) | |
| 
 | |
| 	// Make one reservation "old" | |
| 	cr.Lock() | |
| 	if reservation, exists := cr.reservations[reservationId1]; exists { | |
| 		reservation.createdAt = time.Now().Add(-10 * time.Minute) // 10 minutes ago | |
| 	} | |
| 	cr.Unlock() | |
| 
 | |
| 	// Clean expired reservations (5 minute expiration) | |
| 	cr.cleanExpiredReservations(5 * time.Minute) | |
| 
 | |
| 	// Only the non-expired reservation should remain | |
| 	if count := cr.getReservedCount(diskType); count != 2 { | |
| 		t.Errorf("Expected 2 reserved count after cleaning, got %d", count) | |
| 	} | |
| 
 | |
| 	// Verify the right reservation was kept | |
| 	if !cr.removeReservation(reservationId2) { | |
| 		t.Error("Expected recent reservation to still exist") | |
| 	} | |
| 
 | |
| 	if cr.removeReservation(reservationId1) { | |
| 		t.Error("Expected old reservation to be cleaned up") | |
| 	} | |
| } | |
| 
 | |
| func TestCapacityReservations_DifferentDiskTypes(t *testing.T) { | |
| 	cr := newCapacityReservations() | |
| 
 | |
| 	// Add reservations for different disk types | |
| 	cr.addReservation(types.HardDriveType, 5) | |
| 	cr.addReservation(types.SsdType, 3) | |
| 
 | |
| 	// Check counts are separate | |
| 	if count := cr.getReservedCount(types.HardDriveType); count != 5 { | |
| 		t.Errorf("Expected 5 HDD reserved count, got %d", count) | |
| 	} | |
| 
 | |
| 	if count := cr.getReservedCount(types.SsdType); count != 3 { | |
| 		t.Errorf("Expected 3 SSD reserved count, got %d", count) | |
| 	} | |
| } | |
| 
 | |
| func TestNodeImpl_ReservationMethods(t *testing.T) { | |
| 	// Create a test data node | |
| 	dn := NewDataNode("test-node") | |
| 	diskType := types.HardDriveType | |
| 
 | |
| 	// Set up some capacity | |
| 	diskUsage := dn.diskUsages.getOrCreateDisk(diskType) | |
| 	diskUsage.maxVolumeCount = 10 | |
| 	diskUsage.volumeCount = 5 // 5 volumes free initially | |
|  | |
| 	option := &VolumeGrowOption{DiskType: diskType} | |
| 
 | |
| 	// Test available space calculation | |
| 	available := dn.AvailableSpaceFor(option) | |
| 	if available != 5 { | |
| 		t.Errorf("Expected 5 available slots, got %d", available) | |
| 	} | |
| 
 | |
| 	availableForReservation := dn.AvailableSpaceForReservation(option) | |
| 	if availableForReservation != 5 { | |
| 		t.Errorf("Expected 5 available slots for reservation, got %d", availableForReservation) | |
| 	} | |
| 
 | |
| 	// Test successful reservation | |
| 	reservationId, success := dn.TryReserveCapacity(diskType, 3) | |
| 	if !success { | |
| 		t.Error("Expected successful reservation") | |
| 	} | |
| 	if reservationId == "" { | |
| 		t.Error("Expected non-empty reservation ID") | |
| 	} | |
| 
 | |
| 	// Available space should be reduced by reservations | |
| 	availableForReservation = dn.AvailableSpaceForReservation(option) | |
| 	if availableForReservation != 2 { | |
| 		t.Errorf("Expected 2 available slots after reservation, got %d", availableForReservation) | |
| 	} | |
| 
 | |
| 	// Base available space should remain unchanged | |
| 	available = dn.AvailableSpaceFor(option) | |
| 	if available != 5 { | |
| 		t.Errorf("Expected base available to remain 5, got %d", available) | |
| 	} | |
| 
 | |
| 	// Test reservation failure when insufficient capacity | |
| 	_, success = dn.TryReserveCapacity(diskType, 3) | |
| 	if success { | |
| 		t.Error("Expected reservation failure due to insufficient capacity") | |
| 	} | |
| 
 | |
| 	// Test release reservation | |
| 	dn.ReleaseReservedCapacity(reservationId) | |
| 	availableForReservation = dn.AvailableSpaceForReservation(option) | |
| 	if availableForReservation != 5 { | |
| 		t.Errorf("Expected 5 available slots after release, got %d", availableForReservation) | |
| 	} | |
| } | |
| 
 | |
| func TestNodeImpl_ConcurrentReservations(t *testing.T) { | |
| 	dn := NewDataNode("test-node") | |
| 	diskType := types.HardDriveType | |
| 
 | |
| 	// Set up capacity | |
| 	diskUsage := dn.diskUsages.getOrCreateDisk(diskType) | |
| 	diskUsage.maxVolumeCount = 10 | |
| 	diskUsage.volumeCount = 0 // 10 volumes free initially | |
|  | |
| 	// Test concurrent reservations using goroutines | |
| 	var wg sync.WaitGroup | |
| 	var reservationIds sync.Map | |
| 	concurrentRequests := 10 | |
| 	wg.Add(concurrentRequests) | |
| 
 | |
| 	for i := 0; i < concurrentRequests; i++ { | |
| 		go func(i int) { | |
| 			defer wg.Done() | |
| 			if reservationId, success := dn.TryReserveCapacity(diskType, 1); success { | |
| 				reservationIds.Store(reservationId, true) | |
| 				t.Logf("goroutine %d: Successfully reserved %s", i, reservationId) | |
| 			} else { | |
| 				t.Errorf("goroutine %d: Expected successful reservation", i) | |
| 			} | |
| 		}(i) | |
| 	} | |
| 
 | |
| 	wg.Wait() | |
| 
 | |
| 	// Should have no more capacity | |
| 	option := &VolumeGrowOption{DiskType: diskType} | |
| 	if available := dn.AvailableSpaceForReservation(option); available != 0 { | |
| 		t.Errorf("Expected 0 available slots after all reservations, got %d", available) | |
| 		// Debug: check total reserved | |
| 		reservedCount := dn.capacityReservations.getReservedCount(diskType) | |
| 		t.Logf("Debug: Total reserved count: %d", reservedCount) | |
| 	} | |
| 
 | |
| 	// Next reservation should fail | |
| 	_, success := dn.TryReserveCapacity(diskType, 1) | |
| 	if success { | |
| 		t.Error("Expected reservation failure when at capacity") | |
| 	} | |
| 
 | |
| 	// Release all reservations | |
| 	reservationIds.Range(func(key, value interface{}) bool { | |
| 		dn.ReleaseReservedCapacity(key.(string)) | |
| 		return true | |
| 	}) | |
| 
 | |
| 	// Should have full capacity back | |
| 	if available := dn.AvailableSpaceForReservation(option); available != 10 { | |
| 		t.Errorf("Expected 10 available slots after releasing all, got %d", available) | |
| 	} | |
| }
 |