aboutsummaryrefslogtreecommitdiff
path: root/weed/topology/capacity_reservation_test.go
blob: 38cb14c505db513f7349473f28ed55f6d1c64817 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
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)
	}
}