github.com/bigcommerce/nomad@v0.9.3-bc/client/allocwatcher/group_alloc_watcher_test.go (about)

     1  package allocwatcher
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"testing"
     7  
     8  	"github.com/hashicorp/nomad/nomad/structs"
     9  	"github.com/hashicorp/nomad/testutil"
    10  	"github.com/stretchr/testify/require"
    11  )
    12  
    13  // TestPrevAlloc_GroupPrevAllocWatcher_Block asserts that when there are
    14  // prevAllocs is set a groupPrevAllocWatcher will block on them
    15  func TestPrevAlloc_GroupPrevAllocWatcher_Block(t *testing.T) {
    16  	t.Parallel()
    17  	conf, cleanup := newConfig(t)
    18  
    19  	defer cleanup()
    20  
    21  	conf.Alloc.Job.TaskGroups[0].Tasks[0].Config = map[string]interface{}{
    22  		"run_for": "500ms",
    23  	}
    24  
    25  	waiter, _ := NewAllocWatcher(conf)
    26  
    27  	groupWaiter := &groupPrevAllocWatcher{prevAllocs: []PrevAllocWatcher{waiter}}
    28  
    29  	// Wait in a goroutine with a context to make sure it exits at the right time
    30  	ctx, cancel := context.WithCancel(context.Background())
    31  	defer cancel()
    32  	go func() {
    33  		defer cancel()
    34  		groupWaiter.Wait(ctx)
    35  	}()
    36  
    37  	// Assert watcher is waiting
    38  	testutil.WaitForResult(func() (bool, error) {
    39  		return groupWaiter.IsWaiting(), fmt.Errorf("expected watcher to be waiting")
    40  	}, func(err error) {
    41  		t.Fatalf("error: %v", err)
    42  	})
    43  
    44  	// Broadcast a non-terminal alloc update to assert only terminal
    45  	// updates break out of waiting.
    46  	update := conf.PreviousRunner.Alloc().Copy()
    47  	update.DesiredStatus = structs.AllocDesiredStatusStop
    48  	update.ModifyIndex++
    49  	update.AllocModifyIndex++
    50  
    51  	broadcaster := conf.PreviousRunner.(*fakeAllocRunner).Broadcaster
    52  	err := broadcaster.Send(update)
    53  	require.NoError(t, err)
    54  
    55  	// Assert watcher is still waiting because alloc isn't terminal
    56  	testutil.WaitForResult(func() (bool, error) {
    57  		return groupWaiter.IsWaiting(), fmt.Errorf("expected watcher to be waiting")
    58  	}, func(err error) {
    59  		t.Fatalf("error: %v", err)
    60  	})
    61  
    62  	// Stop the previous alloc and assert watcher stops blocking
    63  	update = update.Copy()
    64  	update.DesiredStatus = structs.AllocDesiredStatusStop
    65  	update.ClientStatus = structs.AllocClientStatusComplete
    66  	update.ModifyIndex++
    67  	update.AllocModifyIndex++
    68  
    69  	err = broadcaster.Send(update)
    70  	require.NoError(t, err)
    71  
    72  	testutil.WaitForResult(func() (bool, error) {
    73  		return !groupWaiter.IsWaiting(), fmt.Errorf("did not expect watcher to be waiting")
    74  	}, func(err error) {
    75  		t.Fatalf("error: %v", err)
    76  	})
    77  }
    78  
    79  // TestPrevAlloc_GroupPrevAllocWatcher_BlockMulti asserts that when there are
    80  // multiple prevAllocs is set a groupPrevAllocWatcher will block until all
    81  // are complete
    82  func TestPrevAlloc_GroupPrevAllocWatcher_BlockMulti(t *testing.T) {
    83  	t.Parallel()
    84  	conf1, cleanup1 := newConfig(t)
    85  	defer cleanup1()
    86  	conf1.Alloc.Job.TaskGroups[0].Tasks[0].Config = map[string]interface{}{
    87  		"run_for": "500ms",
    88  	}
    89  
    90  	conf2, cleanup2 := newConfig(t)
    91  	defer cleanup2()
    92  	conf2.Alloc.Job.TaskGroups[0].Tasks[0].Config = map[string]interface{}{
    93  		"run_for": "500ms",
    94  	}
    95  
    96  	waiter1, _ := NewAllocWatcher(conf1)
    97  	waiter2, _ := NewAllocWatcher(conf2)
    98  
    99  	groupWaiter := &groupPrevAllocWatcher{
   100  		prevAllocs: []PrevAllocWatcher{
   101  			waiter1,
   102  			waiter2,
   103  		},
   104  	}
   105  
   106  	terminalBroadcastFn := func(cfg Config) {
   107  		update := cfg.PreviousRunner.Alloc().Copy()
   108  		update.DesiredStatus = structs.AllocDesiredStatusStop
   109  		update.ClientStatus = structs.AllocClientStatusComplete
   110  		update.ModifyIndex++
   111  		update.AllocModifyIndex++
   112  
   113  		broadcaster := cfg.PreviousRunner.(*fakeAllocRunner).Broadcaster
   114  		err := broadcaster.Send(update)
   115  		require.NoError(t, err)
   116  	}
   117  
   118  	// Wait in a goroutine with a context to make sure it exits at the right time
   119  	ctx, cancel := context.WithCancel(context.Background())
   120  	defer cancel()
   121  	go func() {
   122  		defer cancel()
   123  		groupWaiter.Wait(ctx)
   124  	}()
   125  
   126  	// Assert watcher is waiting
   127  	testutil.WaitForResult(func() (bool, error) {
   128  		return groupWaiter.IsWaiting(), fmt.Errorf("expected watcher to be waiting")
   129  	}, func(err error) {
   130  		t.Fatalf("error: %v", err)
   131  	})
   132  
   133  	// Broadcast a terminal alloc update to the first watcher
   134  	terminalBroadcastFn(conf1)
   135  
   136  	// Assert watcher is still waiting because alloc isn't terminal
   137  	testutil.WaitForResult(func() (bool, error) {
   138  		return groupWaiter.IsWaiting(), fmt.Errorf("expected watcher to be waiting")
   139  	}, func(err error) {
   140  		t.Fatalf("error: %v", err)
   141  	})
   142  
   143  	// Broadcast a terminal alloc update to the second watcher
   144  	terminalBroadcastFn(conf2)
   145  
   146  	testutil.WaitForResult(func() (bool, error) {
   147  		return !groupWaiter.IsWaiting(), fmt.Errorf("did not expect watcher to be waiting")
   148  	}, func(err error) {
   149  		t.Fatalf("error: %v", err)
   150  	})
   151  }