github.com/superfly/nomad@v0.10.5-fly/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  		eligibilityReq := api.NodeUpdateEligibilityRequest{
   374  			Eligibility: structs.NodeSchedulingIneligible,
   375  		}
   376  
   377  		// Make the HTTP request
   378  		buf := encodeReq(eligibilityReq)
   379  		req, err := http.NewRequest("POST", "/v1/node/"+node.ID+"/eligibility", buf)
   380  		require.Nil(err)
   381  		respW := httptest.NewRecorder()
   382  
   383  		// Make the request
   384  		obj, err := s.Server.NodeSpecificRequest(respW, req)
   385  		require.Nil(err)
   386  
   387  		// Check for the index
   388  		require.NotZero(respW.HeaderMap.Get("X-Nomad-Index"))
   389  
   390  		// Check the response
   391  		_, ok := obj.(structs.NodeEligibilityUpdateResponse)
   392  		require.True(ok)
   393  
   394  		// Check that the node has been updated
   395  		state := s.Agent.server.State()
   396  		out, err := state.NodeByID(nil, node.ID)
   397  		require.Nil(err)
   398  		require.Equal(structs.NodeSchedulingIneligible, out.SchedulingEligibility)
   399  
   400  		// Make the HTTP request to set something invalid
   401  		eligibilityReq.Eligibility = "foo"
   402  		buf = encodeReq(eligibilityReq)
   403  		req, err = http.NewRequest("POST", "/v1/node/"+node.ID+"/eligibility", buf)
   404  		require.Nil(err)
   405  		respW = httptest.NewRecorder()
   406  
   407  		// Make the request
   408  		_, err = s.Server.NodeSpecificRequest(respW, req)
   409  		require.NotNil(err)
   410  		require.Contains(err.Error(), "invalid")
   411  	})
   412  }
   413  
   414  func TestHTTP_NodePurge(t *testing.T) {
   415  	t.Parallel()
   416  	httpTest(t, nil, func(s *TestAgent) {
   417  		// Create the node
   418  		node := mock.Node()
   419  		args := structs.NodeRegisterRequest{
   420  			Node:         node,
   421  			WriteRequest: structs.WriteRequest{Region: "global"},
   422  		}
   423  		var resp structs.NodeUpdateResponse
   424  		if err := s.Agent.RPC("Node.Register", &args, &resp); err != nil {
   425  			t.Fatalf("err: %v", err)
   426  		}
   427  
   428  		// Add some allocations to the node
   429  		state := s.Agent.server.State()
   430  		alloc1 := mock.Alloc()
   431  		alloc1.NodeID = node.ID
   432  		if err := state.UpsertJobSummary(999, mock.JobSummary(alloc1.JobID)); err != nil {
   433  			t.Fatal(err)
   434  		}
   435  		err := state.UpsertAllocs(1000, []*structs.Allocation{alloc1})
   436  		if err != nil {
   437  			t.Fatalf("err: %v", err)
   438  		}
   439  
   440  		// Make the HTTP request to purge it
   441  		req, err := http.NewRequest("POST", "/v1/node/"+node.ID+"/purge", nil)
   442  		if err != nil {
   443  			t.Fatalf("err: %v", err)
   444  		}
   445  		respW := httptest.NewRecorder()
   446  
   447  		// Make the request
   448  		obj, err := s.Server.NodeSpecificRequest(respW, req)
   449  		if err != nil {
   450  			t.Fatalf("err: %v", err)
   451  		}
   452  
   453  		// Check for the index
   454  		if respW.HeaderMap.Get("X-Nomad-Index") == "" {
   455  			t.Fatalf("missing index")
   456  		}
   457  
   458  		// Check the response
   459  		upd := obj.(structs.NodeUpdateResponse)
   460  		if len(upd.EvalIDs) == 0 {
   461  			t.Fatalf("bad: %v", upd)
   462  		}
   463  
   464  		// Ensure that the node is not present anymore
   465  		args1 := structs.NodeSpecificRequest{
   466  			NodeID:       node.ID,
   467  			QueryOptions: structs.QueryOptions{Region: "global"},
   468  		}
   469  		var resp1 structs.SingleNodeResponse
   470  		if err := s.Agent.RPC("Node.GetNode", &args1, &resp1); err != nil {
   471  			t.Fatalf("err: %v", err)
   472  		}
   473  		if resp1.Node != nil {
   474  			t.Fatalf("node still exists after purging: %#v", resp1.Node)
   475  		}
   476  	})
   477  }
   478  
   479  func TestHTTP_NodeQuery(t *testing.T) {
   480  	t.Parallel()
   481  	httpTest(t, nil, func(s *TestAgent) {
   482  		// Create the job
   483  		node := mock.Node()
   484  		args := structs.NodeRegisterRequest{
   485  			Node:         node,
   486  			WriteRequest: structs.WriteRequest{Region: "global"},
   487  		}
   488  		var resp structs.NodeUpdateResponse
   489  		if err := s.Agent.RPC("Node.Register", &args, &resp); err != nil {
   490  			t.Fatalf("err: %v", err)
   491  		}
   492  
   493  		// Make the HTTP request
   494  		req, err := http.NewRequest("GET", "/v1/node/"+node.ID, nil)
   495  		if err != nil {
   496  			t.Fatalf("err: %v", err)
   497  		}
   498  		respW := httptest.NewRecorder()
   499  
   500  		// Make the request
   501  		obj, err := s.Server.NodeSpecificRequest(respW, req)
   502  		if err != nil {
   503  			t.Fatalf("err: %v", err)
   504  		}
   505  
   506  		// Check for the index
   507  		if respW.HeaderMap.Get("X-Nomad-Index") == "" {
   508  			t.Fatalf("missing index")
   509  		}
   510  		if respW.HeaderMap.Get("X-Nomad-KnownLeader") != "true" {
   511  			t.Fatalf("missing known leader")
   512  		}
   513  		if respW.HeaderMap.Get("X-Nomad-LastContact") == "" {
   514  			t.Fatalf("missing last contact")
   515  		}
   516  
   517  		// Check the node
   518  		n := obj.(*structs.Node)
   519  		if n.ID != node.ID {
   520  			t.Fatalf("bad: %#v", n)
   521  		}
   522  		if len(n.Events) < 1 {
   523  			t.Fatalf("Expected node registration event to be populated: %#v", n)
   524  		}
   525  		if n.Events[0].Message != "Node registered" {
   526  			t.Fatalf("Expected node registration event to be first node event: %#v", n)
   527  		}
   528  	})
   529  }