github.com/diptanu/nomad@v0.5.7-0.20170516172507-d72e86cbe3d9/client/gc_test.go (about) 1 package client 2 3 import ( 4 "log" 5 "os" 6 "testing" 7 "time" 8 9 "github.com/hashicorp/nomad/client/stats" 10 "github.com/hashicorp/nomad/nomad/mock" 11 "github.com/hashicorp/nomad/nomad/structs" 12 ) 13 14 var gcConfig = GCConfig{ 15 DiskUsageThreshold: 80, 16 InodeUsageThreshold: 70, 17 Interval: 1 * time.Minute, 18 ReservedDiskMB: 0, 19 } 20 21 func TestIndexedGCAllocPQ(t *testing.T) { 22 pq := NewIndexedGCAllocPQ() 23 24 _, ar1 := testAllocRunnerFromAlloc(mock.Alloc(), false) 25 _, ar2 := testAllocRunnerFromAlloc(mock.Alloc(), false) 26 _, ar3 := testAllocRunnerFromAlloc(mock.Alloc(), false) 27 _, ar4 := testAllocRunnerFromAlloc(mock.Alloc(), false) 28 29 pq.Push(ar1) 30 pq.Push(ar2) 31 pq.Push(ar3) 32 pq.Push(ar4) 33 34 allocID := pq.Pop().allocRunner.Alloc().ID 35 if allocID != ar1.Alloc().ID { 36 t.Fatalf("expected alloc %v, got %v", allocID, ar1.Alloc().ID) 37 } 38 39 allocID = pq.Pop().allocRunner.Alloc().ID 40 if allocID != ar2.Alloc().ID { 41 t.Fatalf("expected alloc %v, got %v", allocID, ar1.Alloc().ID) 42 } 43 44 allocID = pq.Pop().allocRunner.Alloc().ID 45 if allocID != ar3.Alloc().ID { 46 t.Fatalf("expected alloc %v, got %v", allocID, ar1.Alloc().ID) 47 } 48 49 allocID = pq.Pop().allocRunner.Alloc().ID 50 if allocID != ar4.Alloc().ID { 51 t.Fatalf("expected alloc %v, got %v", allocID, ar1.Alloc().ID) 52 } 53 54 gcAlloc := pq.Pop() 55 if gcAlloc != nil { 56 t.Fatalf("expected nil, got %v", gcAlloc) 57 } 58 } 59 60 type MockStatsCollector struct { 61 availableValues []uint64 62 usedPercents []float64 63 inodePercents []float64 64 index int 65 } 66 67 func (m *MockStatsCollector) Collect() error { 68 return nil 69 } 70 71 func (m *MockStatsCollector) Stats() *stats.HostStats { 72 if len(m.availableValues) == 0 { 73 return nil 74 } 75 76 available := m.availableValues[m.index] 77 usedPercent := m.usedPercents[m.index] 78 inodePercent := m.inodePercents[m.index] 79 80 if m.index < len(m.availableValues)-1 { 81 m.index = m.index + 1 82 } 83 return &stats.HostStats{ 84 AllocDirStats: &stats.DiskStats{ 85 Available: available, 86 UsedPercent: usedPercent, 87 InodesUsedPercent: inodePercent, 88 }, 89 } 90 } 91 92 func TestAllocGarbageCollector_MarkForCollection(t *testing.T) { 93 logger := log.New(os.Stdout, "", 0) 94 gc := NewAllocGarbageCollector(logger, &MockStatsCollector{}, &gcConfig) 95 96 _, ar1 := testAllocRunnerFromAlloc(mock.Alloc(), false) 97 if err := gc.MarkForCollection(ar1); err != nil { 98 t.Fatalf("err: %v", err) 99 } 100 101 gcAlloc := gc.allocRunners.Pop() 102 if gcAlloc == nil || gcAlloc.allocRunner != ar1 { 103 t.Fatalf("bad gcAlloc: %v", gcAlloc) 104 } 105 } 106 107 func TestAllocGarbageCollector_Collect(t *testing.T) { 108 logger := log.New(os.Stdout, "", 0) 109 gc := NewAllocGarbageCollector(logger, &MockStatsCollector{}, &gcConfig) 110 111 _, ar1 := testAllocRunnerFromAlloc(mock.Alloc(), false) 112 _, ar2 := testAllocRunnerFromAlloc(mock.Alloc(), false) 113 if err := gc.MarkForCollection(ar1); err != nil { 114 t.Fatalf("err: %v", err) 115 } 116 if err := gc.MarkForCollection(ar2); err != nil { 117 t.Fatalf("err: %v", err) 118 } 119 120 // Fake that ar.Run() exits 121 close(ar1.waitCh) 122 close(ar2.waitCh) 123 124 if err := gc.Collect(ar1.Alloc().ID); err != nil { 125 t.Fatalf("err: %v", err) 126 } 127 gcAlloc := gc.allocRunners.Pop() 128 if gcAlloc == nil || gcAlloc.allocRunner != ar2 { 129 t.Fatalf("bad gcAlloc: %v", gcAlloc) 130 } 131 } 132 133 func TestAllocGarbageCollector_CollectAll(t *testing.T) { 134 logger := log.New(os.Stdout, "", 0) 135 gc := NewAllocGarbageCollector(logger, &MockStatsCollector{}, &gcConfig) 136 137 _, ar1 := testAllocRunnerFromAlloc(mock.Alloc(), false) 138 _, ar2 := testAllocRunnerFromAlloc(mock.Alloc(), false) 139 if err := gc.MarkForCollection(ar1); err != nil { 140 t.Fatalf("err: %v", err) 141 } 142 if err := gc.MarkForCollection(ar2); err != nil { 143 t.Fatalf("err: %v", err) 144 } 145 146 if err := gc.CollectAll(); err != nil { 147 t.Fatalf("err: %v", err) 148 } 149 gcAlloc := gc.allocRunners.Pop() 150 if gcAlloc != nil { 151 t.Fatalf("bad gcAlloc: %v", gcAlloc) 152 } 153 } 154 155 func TestAllocGarbageCollector_MakeRoomForAllocations_EnoughSpace(t *testing.T) { 156 logger := log.New(os.Stdout, "", 0) 157 statsCollector := &MockStatsCollector{} 158 gcConfig.ReservedDiskMB = 20 159 gc := NewAllocGarbageCollector(logger, statsCollector, &gcConfig) 160 161 _, ar1 := testAllocRunnerFromAlloc(mock.Alloc(), false) 162 close(ar1.waitCh) 163 _, ar2 := testAllocRunnerFromAlloc(mock.Alloc(), false) 164 close(ar2.waitCh) 165 if err := gc.MarkForCollection(ar1); err != nil { 166 t.Fatalf("err: %v", err) 167 } 168 if err := gc.MarkForCollection(ar2); err != nil { 169 t.Fatalf("err: %v", err) 170 } 171 172 // Make stats collector report 200MB free out of which 20MB is reserved 173 statsCollector.availableValues = []uint64{200 * MB} 174 statsCollector.usedPercents = []float64{0} 175 statsCollector.inodePercents = []float64{0} 176 177 alloc := mock.Alloc() 178 alloc.Resources.DiskMB = 150 179 if err := gc.MakeRoomFor([]*structs.Allocation{alloc}); err != nil { 180 t.Fatalf("err: %v", err) 181 } 182 183 // When we have enough disk available and don't need to do any GC so we 184 // should have two ARs in the GC queue 185 for i := 0; i < 2; i++ { 186 if gcAlloc := gc.allocRunners.Pop(); gcAlloc == nil { 187 t.Fatalf("err: %v", gcAlloc) 188 } 189 } 190 } 191 192 func TestAllocGarbageCollector_MakeRoomForAllocations_GC_Partial(t *testing.T) { 193 logger := log.New(os.Stdout, "", 0) 194 statsCollector := &MockStatsCollector{} 195 gcConfig.ReservedDiskMB = 20 196 gc := NewAllocGarbageCollector(logger, statsCollector, &gcConfig) 197 198 _, ar1 := testAllocRunnerFromAlloc(mock.Alloc(), false) 199 close(ar1.waitCh) 200 _, ar2 := testAllocRunnerFromAlloc(mock.Alloc(), false) 201 close(ar2.waitCh) 202 if err := gc.MarkForCollection(ar1); err != nil { 203 t.Fatalf("err: %v", err) 204 } 205 if err := gc.MarkForCollection(ar2); err != nil { 206 t.Fatalf("err: %v", err) 207 } 208 209 // Make stats collector report 80MB and 175MB free in subsequent calls 210 statsCollector.availableValues = []uint64{80 * MB, 80 * MB, 175 * MB} 211 statsCollector.usedPercents = []float64{0, 0, 0} 212 statsCollector.inodePercents = []float64{0, 0, 0} 213 214 alloc := mock.Alloc() 215 alloc.Resources.DiskMB = 150 216 if err := gc.MakeRoomFor([]*structs.Allocation{alloc}); err != nil { 217 t.Fatalf("err: %v", err) 218 } 219 220 // We should be GC-ing one alloc 221 if gcAlloc := gc.allocRunners.Pop(); gcAlloc == nil { 222 t.Fatalf("err: %v", gcAlloc) 223 } 224 225 if gcAlloc := gc.allocRunners.Pop(); gcAlloc != nil { 226 t.Fatalf("gcAlloc: %v", gcAlloc) 227 } 228 } 229 230 func TestAllocGarbageCollector_MakeRoomForAllocations_GC_All(t *testing.T) { 231 logger := log.New(os.Stdout, "", 0) 232 statsCollector := &MockStatsCollector{} 233 gcConfig.ReservedDiskMB = 20 234 gc := NewAllocGarbageCollector(logger, statsCollector, &gcConfig) 235 236 _, ar1 := testAllocRunnerFromAlloc(mock.Alloc(), false) 237 close(ar1.waitCh) 238 _, ar2 := testAllocRunnerFromAlloc(mock.Alloc(), false) 239 close(ar2.waitCh) 240 if err := gc.MarkForCollection(ar1); err != nil { 241 t.Fatalf("err: %v", err) 242 } 243 if err := gc.MarkForCollection(ar2); err != nil { 244 t.Fatalf("err: %v", err) 245 } 246 247 // Make stats collector report 80MB and 95MB free in subsequent calls 248 statsCollector.availableValues = []uint64{80 * MB, 80 * MB, 95 * MB} 249 statsCollector.usedPercents = []float64{0, 0, 0} 250 statsCollector.inodePercents = []float64{0, 0, 0} 251 252 alloc := mock.Alloc() 253 alloc.Resources.DiskMB = 150 254 if err := gc.MakeRoomFor([]*structs.Allocation{alloc}); err != nil { 255 t.Fatalf("err: %v", err) 256 } 257 258 // We should be GC-ing all the alloc runners 259 if gcAlloc := gc.allocRunners.Pop(); gcAlloc != nil { 260 t.Fatalf("gcAlloc: %v", gcAlloc) 261 } 262 } 263 264 func TestAllocGarbageCollector_MakeRoomForAllocations_GC_Fallback(t *testing.T) { 265 logger := log.New(os.Stdout, "", 0) 266 statsCollector := &MockStatsCollector{} 267 gcConfig.ReservedDiskMB = 20 268 gc := NewAllocGarbageCollector(logger, statsCollector, &gcConfig) 269 270 _, ar1 := testAllocRunnerFromAlloc(mock.Alloc(), false) 271 close(ar1.waitCh) 272 _, ar2 := testAllocRunnerFromAlloc(mock.Alloc(), false) 273 close(ar2.waitCh) 274 if err := gc.MarkForCollection(ar1); err != nil { 275 t.Fatalf("err: %v", err) 276 } 277 if err := gc.MarkForCollection(ar2); err != nil { 278 t.Fatalf("err: %v", err) 279 } 280 281 alloc := mock.Alloc() 282 alloc.Resources.DiskMB = 150 283 if err := gc.MakeRoomFor([]*structs.Allocation{alloc}); err != nil { 284 t.Fatalf("err: %v", err) 285 } 286 287 // We should be GC-ing one alloc 288 if gcAlloc := gc.allocRunners.Pop(); gcAlloc == nil { 289 t.Fatalf("err: %v", gcAlloc) 290 } 291 292 if gcAlloc := gc.allocRunners.Pop(); gcAlloc != nil { 293 t.Fatalf("gcAlloc: %v", gcAlloc) 294 } 295 } 296 297 func TestAllocGarbageCollector_UsageBelowThreshold(t *testing.T) { 298 logger := log.New(os.Stdout, "", 0) 299 statsCollector := &MockStatsCollector{} 300 gcConfig.ReservedDiskMB = 20 301 gc := NewAllocGarbageCollector(logger, statsCollector, &gcConfig) 302 303 _, ar1 := testAllocRunnerFromAlloc(mock.Alloc(), false) 304 close(ar1.waitCh) 305 _, ar2 := testAllocRunnerFromAlloc(mock.Alloc(), false) 306 close(ar2.waitCh) 307 if err := gc.MarkForCollection(ar1); err != nil { 308 t.Fatalf("err: %v", err) 309 } 310 if err := gc.MarkForCollection(ar2); err != nil { 311 t.Fatalf("err: %v", err) 312 } 313 314 statsCollector.availableValues = []uint64{1000} 315 statsCollector.usedPercents = []float64{20} 316 statsCollector.inodePercents = []float64{10} 317 318 if err := gc.keepUsageBelowThreshold(); err != nil { 319 t.Fatalf("err: %v", err) 320 } 321 322 // We shouldn't GC any of the allocs since the used percent values are below 323 // threshold 324 for i := 0; i < 2; i++ { 325 if gcAlloc := gc.allocRunners.Pop(); gcAlloc == nil { 326 t.Fatalf("err: %v", gcAlloc) 327 } 328 } 329 } 330 331 func TestAllocGarbageCollector_UsedPercentThreshold(t *testing.T) { 332 logger := log.New(os.Stdout, "", 0) 333 statsCollector := &MockStatsCollector{} 334 gcConfig.ReservedDiskMB = 20 335 gc := NewAllocGarbageCollector(logger, statsCollector, &gcConfig) 336 337 _, ar1 := testAllocRunnerFromAlloc(mock.Alloc(), false) 338 close(ar1.waitCh) 339 _, ar2 := testAllocRunnerFromAlloc(mock.Alloc(), false) 340 close(ar2.waitCh) 341 if err := gc.MarkForCollection(ar1); err != nil { 342 t.Fatalf("err: %v", err) 343 } 344 if err := gc.MarkForCollection(ar2); err != nil { 345 t.Fatalf("err: %v", err) 346 } 347 348 statsCollector.availableValues = []uint64{1000, 800} 349 statsCollector.usedPercents = []float64{85, 60} 350 statsCollector.inodePercents = []float64{50, 30} 351 352 if err := gc.keepUsageBelowThreshold(); err != nil { 353 t.Fatalf("err: %v", err) 354 } 355 356 // We should be GC-ing only one of the alloc runners since the second time 357 // used percent returns a number below threshold. 358 if gcAlloc := gc.allocRunners.Pop(); gcAlloc == nil { 359 t.Fatalf("err: %v", gcAlloc) 360 } 361 362 if gcAlloc := gc.allocRunners.Pop(); gcAlloc != nil { 363 t.Fatalf("gcAlloc: %v", gcAlloc) 364 } 365 }