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  }