github.com/quite/nomad@v0.8.6/nomad/drainer/draining_node_test.go (about)

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