github.com/bigcommerce/nomad@v0.9.3-bc/command/agent/node_endpoint_test.go (about)

     1  package agent
     2  
     3  import (
     4  	"net/http"
     5  	"net/http/httptest"
     6  	"testing"
     7  	"time"
     8  
     9  	"github.com/hashicorp/nomad/api"
    10  	"github.com/hashicorp/nomad/nomad/mock"
    11  	"github.com/hashicorp/nomad/nomad/structs"
    12  	"github.com/stretchr/testify/assert"
    13  	"github.com/stretchr/testify/require"
    14  )
    15  
    16  func TestHTTP_NodesList(t *testing.T) {
    17  	t.Parallel()
    18  	httpTest(t, nil, func(s *TestAgent) {
    19  		for i := 0; i < 3; i++ {
    20  			// Create the node
    21  			node := mock.Node()
    22  			args := structs.NodeRegisterRequest{
    23  				Node:         node,
    24  				WriteRequest: structs.WriteRequest{Region: "global"},
    25  			}
    26  			var resp structs.NodeUpdateResponse
    27  			if err := s.Agent.RPC("Node.Register", &args, &resp); err != nil {
    28  				t.Fatalf("err: %v", err)
    29  			}
    30  		}
    31  
    32  		// Make the HTTP request
    33  		req, err := http.NewRequest("GET", "/v1/nodes", nil)
    34  		if err != nil {
    35  			t.Fatalf("err: %v", err)
    36  		}
    37  		respW := httptest.NewRecorder()
    38  
    39  		// Make the request
    40  		obj, err := s.Server.NodesRequest(respW, req)
    41  		if err != nil {
    42  			t.Fatalf("err: %v", err)
    43  		}
    44  
    45  		// Check for the index
    46  		if respW.HeaderMap.Get("X-Nomad-Index") == "" {
    47  			t.Fatalf("missing index")
    48  		}
    49  		if respW.HeaderMap.Get("X-Nomad-KnownLeader") != "true" {
    50  			t.Fatalf("missing known leader")
    51  		}
    52  		if respW.HeaderMap.Get("X-Nomad-LastContact") == "" {
    53  			t.Fatalf("missing last contact")
    54  		}
    55  
    56  		// Check the nodes
    57  		n := obj.([]*structs.NodeListStub)
    58  		if len(n) < 3 { // Maybe 4 including client
    59  			t.Fatalf("bad: %#v", n)
    60  		}
    61  	})
    62  }
    63  
    64  func TestHTTP_NodesPrefixList(t *testing.T) {
    65  	t.Parallel()
    66  	httpTest(t, nil, func(s *TestAgent) {
    67  		ids := []string{
    68  			"12345678-abcd-efab-cdef-123456789abc",
    69  			"12345678-aaaa-efab-cdef-123456789abc",
    70  			"1234aaaa-abcd-efab-cdef-123456789abc",
    71  			"1234bbbb-abcd-efab-cdef-123456789abc",
    72  			"1234cccc-abcd-efab-cdef-123456789abc",
    73  			"1234dddd-abcd-efab-cdef-123456789abc",
    74  		}
    75  		for i := 0; i < 5; i++ {
    76  			// Create the node
    77  			node := mock.Node()
    78  			node.ID = ids[i]
    79  			args := structs.NodeRegisterRequest{
    80  				Node:         node,
    81  				WriteRequest: structs.WriteRequest{Region: "global"},
    82  			}
    83  			var resp structs.NodeUpdateResponse
    84  			if err := s.Agent.RPC("Node.Register", &args, &resp); err != nil {
    85  				t.Fatalf("err: %v", err)
    86  			}
    87  		}
    88  
    89  		// Make the HTTP request
    90  		req, err := http.NewRequest("GET", "/v1/nodes?prefix=12345678", nil)
    91  		if err != nil {
    92  			t.Fatalf("err: %v", err)
    93  		}
    94  		respW := httptest.NewRecorder()
    95  
    96  		// Make the request
    97  		obj, err := s.Server.NodesRequest(respW, req)
    98  		if err != nil {
    99  			t.Fatalf("err: %v", err)
   100  		}
   101  
   102  		// Check for the index
   103  		if respW.HeaderMap.Get("X-Nomad-Index") == "" {
   104  			t.Fatalf("missing index")
   105  		}
   106  		if respW.HeaderMap.Get("X-Nomad-KnownLeader") != "true" {
   107  			t.Fatalf("missing known leader")
   108  		}
   109  		if respW.HeaderMap.Get("X-Nomad-LastContact") == "" {
   110  			t.Fatalf("missing last contact")
   111  		}
   112  
   113  		// Check the nodes
   114  		n := obj.([]*structs.NodeListStub)
   115  		if len(n) != 2 {
   116  			t.Fatalf("bad: %#v", n)
   117  		}
   118  	})
   119  }
   120  
   121  func TestHTTP_NodeForceEval(t *testing.T) {
   122  	t.Parallel()
   123  	httpTest(t, nil, func(s *TestAgent) {
   124  		// Create the node
   125  		node := mock.Node()
   126  		args := structs.NodeRegisterRequest{
   127  			Node:         node,
   128  			WriteRequest: structs.WriteRequest{Region: "global"},
   129  		}
   130  		var resp structs.NodeUpdateResponse
   131  		if err := s.Agent.RPC("Node.Register", &args, &resp); err != nil {
   132  			t.Fatalf("err: %v", err)
   133  		}
   134  
   135  		// Directly manipulate the state
   136  		state := s.Agent.server.State()
   137  		alloc1 := mock.Alloc()
   138  		alloc1.NodeID = node.ID
   139  		if err := state.UpsertJobSummary(999, mock.JobSummary(alloc1.JobID)); err != nil {
   140  			t.Fatal(err)
   141  		}
   142  		err := state.UpsertAllocs(1000, []*structs.Allocation{alloc1})
   143  		if err != nil {
   144  			t.Fatalf("err: %v", err)
   145  		}
   146  
   147  		// Make the HTTP request
   148  		req, err := http.NewRequest("POST", "/v1/node/"+node.ID+"/evaluate", nil)
   149  		if err != nil {
   150  			t.Fatalf("err: %v", err)
   151  		}
   152  		respW := httptest.NewRecorder()
   153  
   154  		// Make the request
   155  		obj, err := s.Server.NodeSpecificRequest(respW, req)
   156  		if err != nil {
   157  			t.Fatalf("err: %v", err)
   158  		}
   159  
   160  		// Check for the index
   161  		if respW.HeaderMap.Get("X-Nomad-Index") == "" {
   162  			t.Fatalf("missing index")
   163  		}
   164  
   165  		// Check the response
   166  		upd := obj.(structs.NodeUpdateResponse)
   167  		if len(upd.EvalIDs) == 0 {
   168  			t.Fatalf("bad: %v", upd)
   169  		}
   170  	})
   171  }
   172  
   173  func TestHTTP_NodeAllocations(t *testing.T) {
   174  	t.Parallel()
   175  	httpTest(t, nil, func(s *TestAgent) {
   176  		// Create the job
   177  		node := mock.Node()
   178  		args := structs.NodeRegisterRequest{
   179  			Node:         node,
   180  			WriteRequest: structs.WriteRequest{Region: "global"},
   181  		}
   182  		var resp structs.NodeUpdateResponse
   183  		if err := s.Agent.RPC("Node.Register", &args, &resp); err != nil {
   184  			t.Fatalf("err: %v", err)
   185  		}
   186  
   187  		// Directly manipulate the state
   188  		state := s.Agent.server.State()
   189  		alloc1 := mock.Alloc()
   190  		alloc1.NodeID = node.ID
   191  		if err := state.UpsertJobSummary(999, mock.JobSummary(alloc1.JobID)); err != nil {
   192  			t.Fatal(err)
   193  		}
   194  		// Create a test event for the allocation
   195  		testEvent := structs.NewTaskEvent(structs.TaskStarted)
   196  		var events []*structs.TaskEvent
   197  		events = append(events, testEvent)
   198  		taskState := &structs.TaskState{Events: events}
   199  		alloc1.TaskStates = make(map[string]*structs.TaskState)
   200  		alloc1.TaskStates["test"] = taskState
   201  
   202  		err := state.UpsertAllocs(1000, []*structs.Allocation{alloc1})
   203  		if err != nil {
   204  			t.Fatalf("err: %v", err)
   205  		}
   206  
   207  		// Make the HTTP request
   208  		req, err := http.NewRequest("GET", "/v1/node/"+node.ID+"/allocations", nil)
   209  		if err != nil {
   210  			t.Fatalf("err: %v", err)
   211  		}
   212  		respW := httptest.NewRecorder()
   213  
   214  		// Make the request
   215  		obj, err := s.Server.NodeSpecificRequest(respW, req)
   216  		if err != nil {
   217  			t.Fatalf("err: %v", err)
   218  		}
   219  
   220  		// Check for the index
   221  		if respW.HeaderMap.Get("X-Nomad-Index") == "" {
   222  			t.Fatalf("missing index")
   223  		}
   224  		if respW.HeaderMap.Get("X-Nomad-KnownLeader") != "true" {
   225  			t.Fatalf("missing known leader")
   226  		}
   227  		if respW.HeaderMap.Get("X-Nomad-LastContact") == "" {
   228  			t.Fatalf("missing last contact")
   229  		}
   230  
   231  		// Check the node
   232  		allocs := obj.([]*structs.Allocation)
   233  		if len(allocs) != 1 || allocs[0].ID != alloc1.ID {
   234  			t.Fatalf("bad: %#v", allocs)
   235  		}
   236  		expectedDisplayMsg := "Task started by client"
   237  		displayMsg := allocs[0].TaskStates["test"].Events[0].DisplayMessage
   238  		assert.Equal(t, expectedDisplayMsg, displayMsg)
   239  	})
   240  }
   241  
   242  func TestHTTP_NodeDrain(t *testing.T) {
   243  	t.Parallel()
   244  	require := require.New(t)
   245  	httpTest(t, nil, func(s *TestAgent) {
   246  		// Create the node
   247  		node := mock.Node()
   248  		args := structs.NodeRegisterRequest{
   249  			Node:         node,
   250  			WriteRequest: structs.WriteRequest{Region: "global"},
   251  		}
   252  		var resp structs.NodeUpdateResponse
   253  		require.Nil(s.Agent.RPC("Node.Register", &args, &resp))
   254  
   255  		drainReq := api.NodeUpdateDrainRequest{
   256  			NodeID: node.ID,
   257  			DrainSpec: &api.DrainSpec{
   258  				Deadline: 10 * time.Second,
   259  			},
   260  		}
   261  
   262  		// Make the HTTP request
   263  		buf := encodeReq(drainReq)
   264  		req, err := http.NewRequest("POST", "/v1/node/"+node.ID+"/drain", buf)
   265  		require.Nil(err)
   266  		respW := httptest.NewRecorder()
   267  
   268  		// Make the request
   269  		obj, err := s.Server.NodeSpecificRequest(respW, req)
   270  		require.Nil(err)
   271  
   272  		// Check for the index
   273  		require.NotZero(respW.HeaderMap.Get("X-Nomad-Index"))
   274  
   275  		// Check the response
   276  		_, ok := obj.(structs.NodeDrainUpdateResponse)
   277  		require.True(ok)
   278  
   279  		// Check that the node has been updated
   280  		state := s.Agent.server.State()
   281  		out, err := state.NodeByID(nil, node.ID)
   282  		require.Nil(err)
   283  		require.True(out.Drain)
   284  		require.NotNil(out.DrainStrategy)
   285  		require.Equal(10*time.Second, out.DrainStrategy.Deadline)
   286  
   287  		// Make the HTTP request to unset drain
   288  		drainReq.DrainSpec = nil
   289  		buf = encodeReq(drainReq)
   290  		req, err = http.NewRequest("POST", "/v1/node/"+node.ID+"/drain", buf)
   291  		require.Nil(err)
   292  		respW = httptest.NewRecorder()
   293  
   294  		// Make the request
   295  		_, err = s.Server.NodeSpecificRequest(respW, req)
   296  		require.Nil(err)
   297  
   298  		out, err = state.NodeByID(nil, node.ID)
   299  		require.Nil(err)
   300  		require.False(out.Drain)
   301  		require.Nil(out.DrainStrategy)
   302  	})
   303  }
   304  
   305  // Tests backwards compatibility code to support pre 0.8 clients
   306  func TestHTTP_NodeDrain_Compat(t *testing.T) {
   307  	t.Parallel()
   308  	require := require.New(t)
   309  	httpTest(t, nil, func(s *TestAgent) {
   310  		// Create the node
   311  		node := mock.Node()
   312  		args := structs.NodeRegisterRequest{
   313  			Node:         node,
   314  			WriteRequest: structs.WriteRequest{Region: "global"},
   315  		}
   316  		var resp structs.NodeUpdateResponse
   317  		require.Nil(s.Agent.RPC("Node.Register", &args, &resp))
   318  
   319  		// Make the HTTP request
   320  		req, err := http.NewRequest("POST", "/v1/node/"+node.ID+"/drain?enable=true", nil)
   321  		require.Nil(err)
   322  		respW := httptest.NewRecorder()
   323  
   324  		// Make the request
   325  		obj, err := s.Server.NodeSpecificRequest(respW, req)
   326  		require.Nil(err)
   327  
   328  		// Check for the index
   329  		require.NotZero(respW.HeaderMap.Get("X-Nomad-Index"))
   330  
   331  		// Check the response
   332  		_, ok := obj.(structs.NodeDrainUpdateResponse)
   333  		require.True(ok)
   334  
   335  		// Check that the node has been updated
   336  		state := s.Agent.server.State()
   337  		out, err := state.NodeByID(nil, node.ID)
   338  		require.Nil(err)
   339  		require.True(out.Drain)
   340  		require.NotNil(out.DrainStrategy)
   341  		require.Equal(-1*time.Second, out.DrainStrategy.Deadline)
   342  
   343  		// Make the HTTP request to unset drain
   344  		req, err = http.NewRequest("POST", "/v1/node/"+node.ID+"/drain?enable=false", nil)
   345  		require.Nil(err)
   346  		respW = httptest.NewRecorder()
   347  
   348  		// Make the request
   349  		_, err = s.Server.NodeSpecificRequest(respW, req)
   350  		require.Nil(err)
   351  
   352  		out, err = state.NodeByID(nil, node.ID)
   353  		require.Nil(err)
   354  		require.False(out.Drain)
   355  		require.Nil(out.DrainStrategy)
   356  		require.Equal(structs.NodeSchedulingEligible, out.SchedulingEligibility)
   357  	})
   358  }
   359  
   360  func TestHTTP_NodeEligible(t *testing.T) {
   361  	t.Parallel()
   362  	require := require.New(t)
   363  	httpTest(t, nil, func(s *TestAgent) {
   364  		// Create the node
   365  		node := mock.Node()
   366  		args := structs.NodeRegisterRequest{
   367  			Node:         node,
   368  			WriteRequest: structs.WriteRequest{Region: "global"},
   369  		}
   370  		var resp structs.NodeUpdateResponse
   371  		require.Nil(s.Agent.RPC("Node.Register", &args, &resp))
   372  
   373  		drainReq := api.NodeUpdateEligibilityRequest{
   374  			NodeID:      node.ID,
   375  			Eligibility: structs.NodeSchedulingIneligible,
   376  		}
   377  
   378  		// Make the HTTP request
   379  		buf := encodeReq(drainReq)
   380  		req, err := http.NewRequest("POST", "/v1/node/"+node.ID+"/eligibility", buf)
   381  		require.Nil(err)
   382  		respW := httptest.NewRecorder()
   383  
   384  		// Make the request
   385  		obj, err := s.Server.NodeSpecificRequest(respW, req)
   386  		require.Nil(err)
   387  
   388  		// Check for the index
   389  		require.NotZero(respW.HeaderMap.Get("X-Nomad-Index"))
   390  
   391  		// Check the response
   392  		_, ok := obj.(structs.NodeEligibilityUpdateResponse)
   393  		require.True(ok)
   394  
   395  		// Check that the node has been updated
   396  		state := s.Agent.server.State()
   397  		out, err := state.NodeByID(nil, node.ID)
   398  		require.Nil(err)
   399  		require.Equal(structs.NodeSchedulingIneligible, out.SchedulingEligibility)
   400  
   401  		// Make the HTTP request to set something invalid
   402  		drainReq.Eligibility = "foo"
   403  		buf = encodeReq(drainReq)
   404  		req, err = http.NewRequest("POST", "/v1/node/"+node.ID+"/eligibility", buf)
   405  		require.Nil(err)
   406  		respW = httptest.NewRecorder()
   407  
   408  		// Make the request
   409  		_, err = s.Server.NodeSpecificRequest(respW, req)
   410  		require.NotNil(err)
   411  		require.Contains(err.Error(), "invalid")
   412  	})
   413  }
   414  
   415  func TestHTTP_NodePurge(t *testing.T) {
   416  	t.Parallel()
   417  	httpTest(t, nil, func(s *TestAgent) {
   418  		// Create the node
   419  		node := mock.Node()
   420  		args := structs.NodeRegisterRequest{
   421  			Node:         node,
   422  			WriteRequest: structs.WriteRequest{Region: "global"},
   423  		}
   424  		var resp structs.NodeUpdateResponse
   425  		if err := s.Agent.RPC("Node.Register", &args, &resp); err != nil {
   426  			t.Fatalf("err: %v", err)
   427  		}
   428  
   429  		// Add some allocations to the node
   430  		state := s.Agent.server.State()
   431  		alloc1 := mock.Alloc()
   432  		alloc1.NodeID = node.ID
   433  		if err := state.UpsertJobSummary(999, mock.JobSummary(alloc1.JobID)); err != nil {
   434  			t.Fatal(err)
   435  		}
   436  		err := state.UpsertAllocs(1000, []*structs.Allocation{alloc1})
   437  		if err != nil {
   438  			t.Fatalf("err: %v", err)
   439  		}
   440  
   441  		// Make the HTTP request to purge it
   442  		req, err := http.NewRequest("POST", "/v1/node/"+node.ID+"/purge", nil)
   443  		if err != nil {
   444  			t.Fatalf("err: %v", err)
   445  		}
   446  		respW := httptest.NewRecorder()
   447  
   448  		// Make the request
   449  		obj, err := s.Server.NodeSpecificRequest(respW, req)
   450  		if err != nil {
   451  			t.Fatalf("err: %v", err)
   452  		}
   453  
   454  		// Check for the index
   455  		if respW.HeaderMap.Get("X-Nomad-Index") == "" {
   456  			t.Fatalf("missing index")
   457  		}
   458  
   459  		// Check the response
   460  		upd := obj.(structs.NodeUpdateResponse)
   461  		if len(upd.EvalIDs) == 0 {
   462  			t.Fatalf("bad: %v", upd)
   463  		}
   464  
   465  		// Ensure that the node is not present anymore
   466  		args1 := structs.NodeSpecificRequest{
   467  			NodeID:       node.ID,
   468  			QueryOptions: structs.QueryOptions{Region: "global"},
   469  		}
   470  		var resp1 structs.SingleNodeResponse
   471  		if err := s.Agent.RPC("Node.GetNode", &args1, &resp1); err != nil {
   472  			t.Fatalf("err: %v", err)
   473  		}
   474  		if resp1.Node != nil {
   475  			t.Fatalf("node still exists after purging: %#v", resp1.Node)
   476  		}
   477  	})
   478  }
   479  
   480  func TestHTTP_NodeQuery(t *testing.T) {
   481  	t.Parallel()
   482  	httpTest(t, nil, func(s *TestAgent) {
   483  		// Create the job
   484  		node := mock.Node()
   485  		args := structs.NodeRegisterRequest{
   486  			Node:         node,
   487  			WriteRequest: structs.WriteRequest{Region: "global"},
   488  		}
   489  		var resp structs.NodeUpdateResponse
   490  		if err := s.Agent.RPC("Node.Register", &args, &resp); err != nil {
   491  			t.Fatalf("err: %v", err)
   492  		}
   493  
   494  		// Make the HTTP request
   495  		req, err := http.NewRequest("GET", "/v1/node/"+node.ID, nil)
   496  		if err != nil {
   497  			t.Fatalf("err: %v", err)
   498  		}
   499  		respW := httptest.NewRecorder()
   500  
   501  		// Make the request
   502  		obj, err := s.Server.NodeSpecificRequest(respW, req)
   503  		if err != nil {
   504  			t.Fatalf("err: %v", err)
   505  		}
   506  
   507  		// Check for the index
   508  		if respW.HeaderMap.Get("X-Nomad-Index") == "" {
   509  			t.Fatalf("missing index")
   510  		}
   511  		if respW.HeaderMap.Get("X-Nomad-KnownLeader") != "true" {
   512  			t.Fatalf("missing known leader")
   513  		}
   514  		if respW.HeaderMap.Get("X-Nomad-LastContact") == "" {
   515  			t.Fatalf("missing last contact")
   516  		}
   517  
   518  		// Check the node
   519  		n := obj.(*structs.Node)
   520  		if n.ID != node.ID {
   521  			t.Fatalf("bad: %#v", n)
   522  		}
   523  		if len(n.Events) < 1 {
   524  			t.Fatalf("Expected node registration event to be populated: %#v", n)
   525  		}
   526  		if n.Events[0].Message != "Node registered" {
   527  			t.Fatalf("Expected node registration event to be first node event: %#v", n)
   528  		}
   529  	})
   530  }