github.com/anth0d/nomad@v0.0.0-20221214183521-ae3a0a2cad06/e2e/events/events.go (about) 1 package events 2 3 import ( 4 "context" 5 "fmt" 6 "time" 7 8 "github.com/hashicorp/nomad/api" 9 "github.com/hashicorp/nomad/e2e/e2eutil" 10 "github.com/hashicorp/nomad/e2e/framework" 11 "github.com/hashicorp/nomad/helper/uuid" 12 "github.com/hashicorp/nomad/nomad/structs" 13 "github.com/hashicorp/nomad/testutil" 14 "github.com/stretchr/testify/require" 15 ) 16 17 type EventsTest struct { 18 framework.TC 19 jobIDs []string 20 } 21 22 func init() { 23 framework.AddSuites(&framework.TestSuite{ 24 Component: "Events", 25 CanRunLocal: true, 26 Cases: []framework.TestCase{ 27 new(EventsTest), 28 }, 29 }) 30 } 31 32 func (tc *EventsTest) BeforeAll(f *framework.F) { 33 e2eutil.WaitForLeader(f.T(), tc.Nomad()) 34 } 35 36 func (tc *EventsTest) AfterEach(f *framework.F) { 37 nomadClient := tc.Nomad() 38 j := nomadClient.Jobs() 39 40 for _, id := range tc.jobIDs { 41 j.Deregister(id, true, nil) 42 } 43 _, err := e2eutil.Command("nomad", "system", "gc") 44 f.NoError(err) 45 } 46 47 // TestDeploymentEvents registers a job then applies a change 48 // An event stream listening to Deployment Events asserts that 49 // a DeploymentPromotion event is emitted 50 func (tc *EventsTest) TestDeploymentEvents(f *framework.F) { 51 t := f.T() 52 53 nomadClient := tc.Nomad() 54 events := nomadClient.EventStream() 55 56 uuid := uuid.Generate() 57 jobID := fmt.Sprintf("deployment-%s", uuid[0:8]) 58 tc.jobIDs = append(tc.jobIDs, jobID) 59 ctx, cancel := context.WithCancel(context.Background()) 60 defer cancel() 61 62 topics := map[api.Topic][]string{ 63 api.TopicDeployment: {jobID}, 64 } 65 66 var deployEvents []api.Event 67 streamCh, err := events.Stream(ctx, topics, 0, nil) 68 require.NoError(t, err) 69 70 // gather deployment events 71 go func() { 72 for { 73 select { 74 case <-ctx.Done(): 75 return 76 case event := <-streamCh: 77 if event.IsHeartbeat() { 78 continue 79 } 80 81 deployEvents = append(deployEvents, event.Events...) 82 } 83 } 84 }() 85 86 // register job 87 e2eutil.RegisterAndWaitForAllocs(t, nomadClient, "events/input/initial.nomad", jobID, "") 88 89 // update job 90 e2eutil.RegisterAllocs(t, nomadClient, "events/input/deploy.nomad", jobID, "") 91 92 ds := e2eutil.DeploymentsForJob(t, nomadClient, jobID) 93 require.Equal(t, 2, len(ds)) 94 deploy := ds[0] 95 96 // wait for deployment to be running and ready for auto promote 97 e2eutil.WaitForDeployment(t, nomadClient, deploy.ID, structs.DeploymentStatusRunning, structs.DeploymentStatusDescriptionRunningAutoPromotion) 98 99 // ensure there is a deployment promotion event 100 testutil.WaitForResult(func() (bool, error) { 101 for _, e := range deployEvents { 102 if e.Type == "DeploymentPromotion" { 103 return true, nil 104 } 105 } 106 var got []string 107 for _, e := range deployEvents { 108 got = append(got, e.Type) 109 } 110 return false, fmt.Errorf("expected to receive deployment promotion event, got: %#v", got) 111 }, func(e error) { 112 f.NoError(e) 113 }) 114 } 115 116 // TestBlockedEvalEvents applies a job with a large memory requirement. The 117 // event stream checks for a failed task group alloc 118 func (tc *EventsTest) TestBlockedEvalEvents(f *framework.F) { 119 t := f.T() 120 121 nomadClient := tc.Nomad() 122 events := nomadClient.EventStream() 123 124 uuid := uuid.Generate() 125 jobID := fmt.Sprintf("blocked-deploy-%s", uuid[0:8]) 126 tc.jobIDs = append(tc.jobIDs, jobID) 127 ctx, cancel := context.WithCancel(context.Background()) 128 defer cancel() 129 130 topics := map[api.Topic][]string{ 131 api.TopicEvaluation: {"*"}, 132 } 133 134 var evalEvents []api.Event 135 streamCh, err := events.Stream(ctx, topics, 0, nil) 136 require.NoError(t, err) 137 138 // gather deployment events 139 go func() { 140 for { 141 select { 142 case <-ctx.Done(): 143 return 144 case event := <-streamCh: 145 if event.IsHeartbeat() { 146 continue 147 } 148 149 evalEvents = append(evalEvents, event.Events...) 150 } 151 } 152 }() 153 154 // register job 155 e2eutil.Register(jobID, "events/input/large-job.nomad") 156 157 // ensure there is a deployment promotion event 158 testutil.WaitForResult(func() (bool, error) { 159 for _, e := range evalEvents { 160 eval, err := e.Evaluation() 161 if err != nil { 162 return false, fmt.Errorf("event was not an evaluation %w", err) 163 } 164 165 ftg := eval.FailedTGAllocs 166 167 tg, ok := ftg["one"] 168 if !ok { 169 continue 170 } 171 172 mem := tg.DimensionExhausted["memory"] 173 require.NotNil(t, mem, "memory dimension was nil") 174 require.Greater(t, mem, 0, "memory dimension was zero") 175 return true, nil 176 177 } 178 return false, fmt.Errorf("expected blocked eval with memory exhausted, got: %#v", evalEvents) 179 }, func(e error) { 180 require.NoError(t, e) 181 }) 182 } 183 184 // TestStartIndex applies a job, then connects to the stream with a start 185 // index to verify that the events from before the job are not included. 186 func (tc *EventsTest) TestStartIndex(f *framework.F) { 187 t := f.T() 188 189 nomadClient := tc.Nomad() 190 events := nomadClient.EventStream() 191 192 uuid := uuid.Short() 193 noopID := fmt.Sprintf("noop-%s", uuid) 194 jobID := fmt.Sprintf("deployment-%s", uuid) 195 jobID2 := fmt.Sprintf("deployment2-%s", uuid) 196 tc.jobIDs = append(tc.jobIDs, noopID, jobID, jobID2) 197 ctx, cancel := context.WithCancel(context.Background()) 198 defer cancel() 199 200 // register job 201 err := e2eutil.Register(jobID, "events/input/initial.nomad") 202 require.NoError(t, err) 203 204 // The stream request gets the event *closest* to the index, not 205 // the exact match. Although events are written before raft 206 // entries they're written asynchronously, so it's possible to 207 // race and get a raft index from this query higher than the 208 // current head of the event buffer. Ensure the job is running 209 // before we try to get the index, so that we've given the event 210 // enough time to land in the buffer. 211 var job *api.Job 212 f.Eventually(func() bool { 213 job, _, err = nomadClient.Jobs().Info(jobID, nil) 214 if err != nil { 215 return false 216 } 217 return *job.Status == "running" 218 }, 20*time.Second, 200*time.Millisecond, "job should be running") 219 220 startIndex := *job.JobModifyIndex + 1 221 222 topics := map[api.Topic][]string{ 223 api.TopicJob: {"*"}, 224 } 225 226 // starting at Job.ModifyIndex + 1, the next (and only) JobRegistered event that we see 227 // should be from a different job registration 228 streamCh, err := events.Stream(ctx, topics, startIndex, nil) 229 require.NoError(t, err) 230 231 var jobEvents []api.Event 232 // gather job register events 233 go func() { 234 for { 235 select { 236 case <-ctx.Done(): 237 return 238 case event, ok := <-streamCh: 239 if !ok { 240 return 241 } 242 if event.IsHeartbeat() { 243 continue 244 } 245 jobEvents = append(jobEvents, event.Events...) 246 } 247 } 248 }() 249 250 // new job (to make sure we get a JobRegistered event) 251 err = e2eutil.Register(jobID2, "events/input/deploy.nomad") 252 require.NoError(t, err) 253 254 // ensure there is a deployment promotion event 255 foundUnexpected := false 256 testutil.WaitForResult(func() (bool, error) { 257 for _, e := range jobEvents { 258 if e.Type == "JobRegistered" { 259 if e.Index < startIndex { 260 foundUnexpected = true 261 } 262 if e.Index >= startIndex { 263 return true, nil 264 } 265 } 266 } 267 return false, fmt.Errorf("expected to receive JobRegistered event for index at least %v", startIndex) 268 }, func(e error) { 269 f.NoError(e) 270 }) 271 require.False(t, foundUnexpected, "found events from earlier-than-expected indices") 272 }