github.com/anth0d/nomad@v0.0.0-20221214183521-ae3a0a2cad06/command/agent/event_endpoint_test.go (about)

     1  package agent
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"net/http"
     7  	"net/http/httptest"
     8  	"net/url"
     9  	"strings"
    10  	"testing"
    11  	"time"
    12  
    13  	"github.com/hashicorp/nomad/api"
    14  	"github.com/hashicorp/nomad/ci"
    15  	"github.com/hashicorp/nomad/helper/uuid"
    16  	"github.com/hashicorp/nomad/nomad/mock"
    17  	"github.com/hashicorp/nomad/nomad/structs"
    18  	"github.com/hashicorp/nomad/testutil"
    19  	"github.com/stretchr/testify/assert"
    20  	"github.com/stretchr/testify/require"
    21  )
    22  
    23  type testEvent struct {
    24  	ID string
    25  }
    26  
    27  func TestEventStream(t *testing.T) {
    28  	ci.Parallel(t)
    29  
    30  	httpTest(t, nil, func(s *TestAgent) {
    31  		ctx, cancel := context.WithCancel(context.Background())
    32  		req, err := http.NewRequestWithContext(ctx, "GET", "/v1/event/stream", nil)
    33  		require.Nil(t, err)
    34  		resp := httptest.NewRecorder()
    35  
    36  		respErrCh := make(chan error)
    37  		go func() {
    38  			_, err = s.Server.EventStream(resp, req)
    39  			respErrCh <- err
    40  			assert.NoError(t, err)
    41  		}()
    42  
    43  		pub, err := s.Agent.server.State().EventBroker()
    44  		require.NoError(t, err)
    45  		pub.Publish(&structs.Events{Index: 100, Events: []structs.Event{{Payload: testEvent{ID: "123"}}}})
    46  
    47  		testutil.WaitForResult(func() (bool, error) {
    48  			got := resp.Body.String()
    49  			want := `{"ID":"123"}`
    50  			if strings.Contains(got, want) {
    51  				return true, nil
    52  			}
    53  
    54  			return false, fmt.Errorf("missing expected json, got: %v, want: %v", got, want)
    55  		}, func(err error) {
    56  			cancel()
    57  			require.Fail(t, err.Error())
    58  		})
    59  
    60  		// wait for response to close to prevent race between subscription
    61  		// shutdown and server shutdown returning subscription closed by server err
    62  		cancel()
    63  		select {
    64  		case err := <-respErrCh:
    65  			require.Nil(t, err)
    66  		case <-time.After(1 * time.Second):
    67  			require.Fail(t, "waiting for request cancellation")
    68  		}
    69  	})
    70  }
    71  
    72  func TestEventStream_NamespaceQuery(t *testing.T) {
    73  	ci.Parallel(t)
    74  
    75  	httpTest(t, nil, func(s *TestAgent) {
    76  		ctx, cancel := context.WithCancel(context.Background())
    77  		defer cancel()
    78  
    79  		req, err := http.NewRequestWithContext(ctx, "GET", "/v1/event/stream?namespace=foo", nil)
    80  		require.Nil(t, err)
    81  		resp := httptest.NewRecorder()
    82  
    83  		respErrCh := make(chan error)
    84  		go func() {
    85  			_, err = s.Server.EventStream(resp, req)
    86  			respErrCh <- err
    87  			assert.NoError(t, err)
    88  		}()
    89  
    90  		pub, err := s.Agent.server.State().EventBroker()
    91  		require.NoError(t, err)
    92  
    93  		badID := uuid.Generate()
    94  		pub.Publish(&structs.Events{Index: 100, Events: []structs.Event{{Namespace: "bar", Payload: testEvent{ID: badID}}}})
    95  		pub.Publish(&structs.Events{Index: 101, Events: []structs.Event{{Namespace: "foo", Payload: testEvent{ID: "456"}}}})
    96  
    97  		testutil.WaitForResult(func() (bool, error) {
    98  			got := resp.Body.String()
    99  			want := `"Namespace":"foo"`
   100  			if strings.Contains(got, badID) {
   101  				return false, fmt.Errorf("expected non matching namespace to be filtered, got:%v", got)
   102  			}
   103  			if strings.Contains(got, want) {
   104  				return true, nil
   105  			}
   106  
   107  			return false, fmt.Errorf("missing expected json, got: %v, want: %v", got, want)
   108  		}, func(err error) {
   109  			require.Fail(t, err.Error())
   110  		})
   111  
   112  		// wait for response to close to prevent race between subscription
   113  		// shutdown and server shutdown returning subscription closed by server err
   114  		cancel()
   115  		select {
   116  		case err := <-respErrCh:
   117  			require.Nil(t, err)
   118  		case <-time.After(1 * time.Second):
   119  			require.Fail(t, "waiting for request cancellation")
   120  		}
   121  	})
   122  }
   123  
   124  func TestEventStream_QueryParse(t *testing.T) {
   125  	ci.Parallel(t)
   126  
   127  	cases := []struct {
   128  		desc    string
   129  		query   string
   130  		want    map[structs.Topic][]string
   131  		wantErr bool
   132  	}{
   133  		{
   134  			desc:  "all topics and keys specified",
   135  			query: "?topic=*:*",
   136  			want: map[structs.Topic][]string{
   137  				"*": {"*"},
   138  			},
   139  		},
   140  		{
   141  			desc:  "all topics and keys inferred",
   142  			query: "",
   143  			want: map[structs.Topic][]string{
   144  				"*": {"*"},
   145  			},
   146  		},
   147  		{
   148  			desc:    "invalid key value formatting",
   149  			query:   "?topic=NodeDrain:*:*",
   150  			wantErr: true,
   151  		},
   152  		{
   153  			desc:    "Infer wildcard if absent",
   154  			query:   "?topic=NodeDrain",
   155  			wantErr: false,
   156  			want: map[structs.Topic][]string{
   157  				"NodeDrain": {"*"},
   158  			},
   159  		},
   160  		{
   161  			desc:  "single topic and key",
   162  			query: "?topic=NodeDrain:*",
   163  			want: map[structs.Topic][]string{
   164  				"NodeDrain": {"*"},
   165  			},
   166  		},
   167  		{
   168  			desc:  "single topic multiple keys",
   169  			query: "?topic=NodeDrain:*&topic=NodeDrain:3caace09-f1f4-4d23-b37a-9ab5eb75069d",
   170  			want: map[structs.Topic][]string{
   171  				"NodeDrain": {
   172  					"*",
   173  					"3caace09-f1f4-4d23-b37a-9ab5eb75069d",
   174  				},
   175  			},
   176  		},
   177  		{
   178  			desc:  "multiple topics",
   179  			query: "?topic=NodeRegister:*&topic=NodeDrain:3caace09-f1f4-4d23-b37a-9ab5eb75069d",
   180  			want: map[structs.Topic][]string{
   181  				"NodeDrain": {
   182  					"3caace09-f1f4-4d23-b37a-9ab5eb75069d",
   183  				},
   184  				"NodeRegister": {
   185  					"*",
   186  				},
   187  			},
   188  		},
   189  	}
   190  
   191  	for _, tc := range cases {
   192  		t.Run(tc.desc, func(t *testing.T) {
   193  			raw := fmt.Sprintf("http://localhost:80/v1/events%s", tc.query)
   194  			req, err := url.Parse(raw)
   195  			require.NoError(t, err)
   196  
   197  			got, err := parseEventTopics(req.Query())
   198  			if tc.wantErr {
   199  				require.Error(t, err)
   200  				return
   201  			}
   202  			require.NoError(t, err)
   203  			require.Equal(t, tc.want, got)
   204  		})
   205  	}
   206  }
   207  
   208  func TestHTTP_Alloc_Port_Response(t *testing.T) {
   209  	ci.Parallel(t)
   210  
   211  	httpTest(t, nil, func(srv *TestAgent) {
   212  		client := srv.Client()
   213  		defer srv.Shutdown()
   214  		defer client.Close()
   215  
   216  		testutil.WaitForLeader(t, srv.Agent.RPC)
   217  		testutil.WaitForClient(t, srv.Agent.Client().RPC, srv.Agent.Client().NodeID(), srv.Agent.Client().Region())
   218  
   219  		job := MockRunnableJob()
   220  
   221  		resp, _, err := client.Jobs().Register(job, nil)
   222  		require.NoError(t, err)
   223  		require.NotEmpty(t, resp.EvalID)
   224  
   225  		alloc := mock.Alloc()
   226  		alloc.Job = ApiJobToStructJob(job)
   227  		alloc.JobID = *job.ID
   228  		alloc.NodeID = srv.client.NodeID()
   229  
   230  		require.Nil(t, srv.server.State().UpsertJobSummary(101, mock.JobSummary(alloc.JobID)))
   231  		require.Nil(t, srv.server.State().UpsertAllocs(structs.MsgTypeTestSetup, 102, []*structs.Allocation{alloc}))
   232  
   233  		running := false
   234  		testutil.WaitForResult(func() (bool, error) {
   235  			upsertResult, stateErr := srv.server.State().AllocByID(nil, alloc.ID)
   236  			if stateErr != nil {
   237  				return false, stateErr
   238  			}
   239  			if upsertResult.ClientStatus == structs.AllocClientStatusRunning {
   240  				running = true
   241  				return true, nil
   242  			}
   243  			return false, nil
   244  		}, func(err error) {
   245  			require.NoError(t, err, "allocation query failed")
   246  		})
   247  
   248  		require.True(t, running)
   249  
   250  		topics := map[api.Topic][]string{
   251  			api.TopicAllocation: {*job.ID},
   252  		}
   253  
   254  		ctx, cancel := context.WithCancel(context.Background())
   255  		defer cancel()
   256  
   257  		events := client.EventStream()
   258  		streamCh, err := events.Stream(ctx, topics, 1, nil)
   259  		require.NoError(t, err)
   260  
   261  		var allocEvents []api.Event
   262  		// gather job alloc events
   263  		go func() {
   264  			for {
   265  				select {
   266  				case event, ok := <-streamCh:
   267  					if !ok {
   268  						return
   269  					}
   270  					if event.IsHeartbeat() {
   271  						continue
   272  					}
   273  					allocEvents = append(allocEvents, event.Events...)
   274  				case <-time.After(10 * time.Second):
   275  					require.Fail(t, "failed waiting for event stream event")
   276  				}
   277  			}
   278  		}()
   279  
   280  		var networkResource *api.NetworkResource
   281  		testutil.WaitForResult(func() (bool, error) {
   282  			for _, e := range allocEvents {
   283  				if e.Type == structs.TypeAllocationUpdated {
   284  					eventAlloc, err := e.Allocation()
   285  					if err != nil {
   286  						return false, err
   287  					}
   288  					if len(eventAlloc.AllocatedResources.Tasks["web"].Networks) == 0 {
   289  						return false, nil
   290  					}
   291  					networkResource = eventAlloc.AllocatedResources.Tasks["web"].Networks[0]
   292  					if networkResource.ReservedPorts[0].Value == 5000 {
   293  						return true, nil
   294  					}
   295  				}
   296  			}
   297  			return false, nil
   298  		}, func(e error) {
   299  			require.NoError(t, err)
   300  		})
   301  
   302  		require.NotNil(t, networkResource)
   303  		require.Equal(t, 5000, networkResource.ReservedPorts[0].Value)
   304  		require.NotEqual(t, 0, networkResource.DynamicPorts[0].Value)
   305  	})
   306  }