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  }