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 }