github.com/anth0d/nomad@v0.0.0-20221214183521-ae3a0a2cad06/nomad/drainer/draining_node_test.go (about)

     1  package drainer
     2  
     3  import (
     4  	"testing"
     5  	"time"
     6  
     7  	"github.com/hashicorp/nomad/ci"
     8  	"github.com/hashicorp/nomad/nomad/mock"
     9  	"github.com/hashicorp/nomad/nomad/state"
    10  	"github.com/hashicorp/nomad/nomad/structs"
    11  	"github.com/stretchr/testify/assert"
    12  	"github.com/stretchr/testify/require"
    13  )
    14  
    15  // testDrainingNode creates a *drainingNode with a 1h deadline but no allocs
    16  func testDrainingNode(t *testing.T) *drainingNode {
    17  	t.Helper()
    18  	state := state.TestStateStore(t)
    19  	node := mock.Node()
    20  	node.DrainStrategy = &structs.DrainStrategy{
    21  		DrainSpec: structs.DrainSpec{
    22  			Deadline: time.Hour,
    23  		},
    24  		ForceDeadline: time.Now().Add(time.Hour),
    25  	}
    26  
    27  	require.Nil(t, state.UpsertNode(structs.MsgTypeTestSetup, 100, node))
    28  	return NewDrainingNode(node, state)
    29  }
    30  
    31  func assertDrainingNode(t *testing.T, dn *drainingNode, isDone bool, remaining, running int) {
    32  	t.Helper()
    33  
    34  	done, err := dn.IsDone()
    35  	require.Nil(t, err)
    36  	assert.Equal(t, isDone, done, "IsDone mismatch")
    37  
    38  	allocs, err := dn.RemainingAllocs()
    39  	require.Nil(t, err)
    40  	assert.Len(t, allocs, remaining, "RemainingAllocs mismatch")
    41  
    42  	jobs, err := dn.DrainingJobs()
    43  	require.Nil(t, err)
    44  	assert.Len(t, jobs, running, "DrainingJobs mismatch")
    45  }
    46  
    47  func TestDrainingNode_Table(t *testing.T) {
    48  	ci.Parallel(t)
    49  
    50  	cases := []struct {
    51  		name      string
    52  		isDone    bool
    53  		remaining int
    54  		running   int
    55  		setup     func(*testing.T, *drainingNode)
    56  	}{
    57  		{
    58  			name:      "Empty",
    59  			isDone:    true,
    60  			remaining: 0,
    61  			running:   0,
    62  			setup:     func(*testing.T, *drainingNode) {},
    63  		},
    64  		{
    65  			name:      "Batch",
    66  			isDone:    false,
    67  			remaining: 1,
    68  			running:   1,
    69  			setup: func(t *testing.T, dn *drainingNode) {
    70  				alloc := mock.BatchAlloc()
    71  				alloc.NodeID = dn.node.ID
    72  				require.Nil(t, dn.state.UpsertJob(structs.MsgTypeTestSetup, 101, alloc.Job))
    73  				require.Nil(t, dn.state.UpsertAllocs(structs.MsgTypeTestSetup, 102, []*structs.Allocation{alloc}))
    74  			},
    75  		},
    76  		{
    77  			name:      "Service",
    78  			isDone:    false,
    79  			remaining: 1,
    80  			running:   1,
    81  			setup: func(t *testing.T, dn *drainingNode) {
    82  				alloc := mock.Alloc()
    83  				alloc.NodeID = dn.node.ID
    84  				require.Nil(t, dn.state.UpsertJob(structs.MsgTypeTestSetup, 101, alloc.Job))
    85  				require.Nil(t, dn.state.UpsertAllocs(structs.MsgTypeTestSetup, 102, []*structs.Allocation{alloc}))
    86  			},
    87  		},
    88  		{
    89  			name:      "System",
    90  			isDone:    true,
    91  			remaining: 1,
    92  			running:   0,
    93  			setup: func(t *testing.T, dn *drainingNode) {
    94  				alloc := mock.SystemAlloc()
    95  				alloc.NodeID = dn.node.ID
    96  				require.Nil(t, dn.state.UpsertJob(structs.MsgTypeTestSetup, 101, alloc.Job))
    97  				require.Nil(t, dn.state.UpsertAllocs(structs.MsgTypeTestSetup, 102, []*structs.Allocation{alloc}))
    98  			},
    99  		},
   100  		{
   101  			name:      "AllTerminal",
   102  			isDone:    true,
   103  			remaining: 0,
   104  			running:   0,
   105  			setup: func(t *testing.T, dn *drainingNode) {
   106  				allocs := []*structs.Allocation{mock.Alloc(), mock.BatchAlloc(), mock.SystemAlloc()}
   107  				for _, a := range allocs {
   108  					a.NodeID = dn.node.ID
   109  					require.Nil(t, dn.state.UpsertJob(structs.MsgTypeTestSetup, 101, a.Job))
   110  				}
   111  				require.Nil(t, dn.state.UpsertAllocs(structs.MsgTypeTestSetup, 102, allocs))
   112  
   113  				// StateStore doesn't like inserting new allocs
   114  				// with a terminal status, so set the status in
   115  				// a second pass
   116  				for _, a := range allocs {
   117  					a.ClientStatus = structs.AllocClientStatusComplete
   118  				}
   119  				require.Nil(t, dn.state.UpsertAllocs(structs.MsgTypeTestSetup, 103, allocs))
   120  			},
   121  		},
   122  		{
   123  			name:      "ServiceTerminal",
   124  			isDone:    false,
   125  			remaining: 2,
   126  			running:   1,
   127  			setup: func(t *testing.T, dn *drainingNode) {
   128  				allocs := []*structs.Allocation{mock.Alloc(), mock.BatchAlloc(), mock.SystemAlloc()}
   129  				for _, a := range allocs {
   130  					a.NodeID = dn.node.ID
   131  					require.Nil(t, dn.state.UpsertJob(structs.MsgTypeTestSetup, 101, a.Job))
   132  				}
   133  				require.Nil(t, dn.state.UpsertAllocs(structs.MsgTypeTestSetup, 102, allocs))
   134  
   135  				// Set only the service job as terminal
   136  				allocs[0].ClientStatus = structs.AllocClientStatusComplete
   137  				require.Nil(t, dn.state.UpsertAllocs(structs.MsgTypeTestSetup, 103, allocs))
   138  			},
   139  		},
   140  		{
   141  			name:      "AllTerminalButBatch",
   142  			isDone:    false,
   143  			remaining: 1,
   144  			running:   1,
   145  			setup: func(t *testing.T, dn *drainingNode) {
   146  				allocs := []*structs.Allocation{mock.Alloc(), mock.BatchAlloc(), mock.SystemAlloc()}
   147  				for _, a := range allocs {
   148  					a.NodeID = dn.node.ID
   149  					require.Nil(t, dn.state.UpsertJob(structs.MsgTypeTestSetup, 101, a.Job))
   150  				}
   151  				require.Nil(t, dn.state.UpsertAllocs(structs.MsgTypeTestSetup, 102, allocs))
   152  
   153  				// Set only the service and batch jobs as terminal
   154  				allocs[0].ClientStatus = structs.AllocClientStatusComplete
   155  				allocs[2].ClientStatus = structs.AllocClientStatusComplete
   156  				require.Nil(t, dn.state.UpsertAllocs(structs.MsgTypeTestSetup, 103, allocs))
   157  			},
   158  		},
   159  		{
   160  			name:      "AllTerminalButSystem",
   161  			isDone:    true,
   162  			remaining: 1,
   163  			running:   0,
   164  			setup: func(t *testing.T, dn *drainingNode) {
   165  				allocs := []*structs.Allocation{mock.Alloc(), mock.BatchAlloc(), mock.SystemAlloc()}
   166  				for _, a := range allocs {
   167  					a.NodeID = dn.node.ID
   168  					require.Nil(t, dn.state.UpsertJob(structs.MsgTypeTestSetup, 101, a.Job))
   169  				}
   170  				require.Nil(t, dn.state.UpsertAllocs(structs.MsgTypeTestSetup, 102, allocs))
   171  
   172  				// Set only the service and batch jobs as terminal
   173  				allocs[0].ClientStatus = structs.AllocClientStatusComplete
   174  				allocs[1].ClientStatus = structs.AllocClientStatusComplete
   175  				require.Nil(t, dn.state.UpsertAllocs(structs.MsgTypeTestSetup, 103, allocs))
   176  			},
   177  		},
   178  		{
   179  			name:      "HalfTerminal",
   180  			isDone:    false,
   181  			remaining: 3,
   182  			running:   2,
   183  			setup: func(t *testing.T, dn *drainingNode) {
   184  				allocs := []*structs.Allocation{
   185  					mock.Alloc(),
   186  					mock.BatchAlloc(),
   187  					mock.SystemAlloc(),
   188  					mock.Alloc(),
   189  					mock.BatchAlloc(),
   190  					mock.SystemAlloc(),
   191  				}
   192  				for _, a := range allocs {
   193  					a.NodeID = dn.node.ID
   194  					require.Nil(t, dn.state.UpsertJob(structs.MsgTypeTestSetup, 101, a.Job))
   195  				}
   196  				require.Nil(t, dn.state.UpsertAllocs(structs.MsgTypeTestSetup, 102, allocs))
   197  
   198  				// Set only the service and batch jobs as terminal
   199  				allocs[0].ClientStatus = structs.AllocClientStatusComplete
   200  				allocs[1].ClientStatus = structs.AllocClientStatusComplete
   201  				allocs[2].ClientStatus = structs.AllocClientStatusComplete
   202  				require.Nil(t, dn.state.UpsertAllocs(structs.MsgTypeTestSetup, 103, allocs))
   203  			},
   204  		},
   205  	}
   206  
   207  	// Default test drainingNode has no allocs, so it should be done and
   208  	// have no remaining allocs.
   209  	for _, tc := range cases {
   210  		tc := tc
   211  		t.Run(tc.name, func(t *testing.T) {
   212  			ci.Parallel(t)
   213  			dn := testDrainingNode(t)
   214  			tc.setup(t, dn)
   215  			assertDrainingNode(t, dn, tc.isDone, tc.remaining, tc.running)
   216  		})
   217  	}
   218  }