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