github.com/thomasobenaus/nomad@v0.11.1/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  		dresp, ok := obj.(structs.NodeDrainUpdateResponse)
   277  		require.True(ok)
   278  
   279  		t.Logf("response index=%v node_update_index=0x%x", respW.HeaderMap.Get("X-Nomad-Index"),
   280  			dresp.NodeModifyIndex)
   281  
   282  		// Check that the node has been updated
   283  		state := s.Agent.server.State()
   284  		out, err := state.NodeByID(nil, node.ID)
   285  		require.Nil(err)
   286  
   287  		// the node must either be in drain mode or in elligible
   288  		// once the node is recognize as not having any running allocs
   289  		if out.Drain {
   290  			require.True(out.Drain)
   291  			require.NotNil(out.DrainStrategy)
   292  			require.Equal(10*time.Second, out.DrainStrategy.Deadline)
   293  		} else {
   294  			require.Equal(structs.NodeSchedulingIneligible, out.SchedulingEligibility)
   295  		}
   296  
   297  		// Make the HTTP request to unset drain
   298  		drainReq.DrainSpec = nil
   299  		buf = encodeReq(drainReq)
   300  		req, err = http.NewRequest("POST", "/v1/node/"+node.ID+"/drain", buf)
   301  		require.Nil(err)
   302  		respW = httptest.NewRecorder()
   303  
   304  		// Make the request
   305  		_, err = s.Server.NodeSpecificRequest(respW, req)
   306  		require.Nil(err)
   307  
   308  		out, err = state.NodeByID(nil, node.ID)
   309  		require.Nil(err)
   310  		require.False(out.Drain)
   311  		require.Nil(out.DrainStrategy)
   312  	})
   313  }
   314  
   315  func TestHTTP_NodeEligible(t *testing.T) {
   316  	t.Parallel()
   317  	require := require.New(t)
   318  	httpTest(t, nil, func(s *TestAgent) {
   319  		// Create the node
   320  		node := mock.Node()
   321  		args := structs.NodeRegisterRequest{
   322  			Node:         node,
   323  			WriteRequest: structs.WriteRequest{Region: "global"},
   324  		}
   325  		var resp structs.NodeUpdateResponse
   326  		require.Nil(s.Agent.RPC("Node.Register", &args, &resp))
   327  
   328  		eligibilityReq := api.NodeUpdateEligibilityRequest{
   329  			Eligibility: structs.NodeSchedulingIneligible,
   330  		}
   331  
   332  		// Make the HTTP request
   333  		buf := encodeReq(eligibilityReq)
   334  		req, err := http.NewRequest("POST", "/v1/node/"+node.ID+"/eligibility", buf)
   335  		require.Nil(err)
   336  		respW := httptest.NewRecorder()
   337  
   338  		// Make the request
   339  		obj, err := s.Server.NodeSpecificRequest(respW, req)
   340  		require.Nil(err)
   341  
   342  		// Check for the index
   343  		require.NotZero(respW.HeaderMap.Get("X-Nomad-Index"))
   344  
   345  		// Check the response
   346  		_, ok := obj.(structs.NodeEligibilityUpdateResponse)
   347  		require.True(ok)
   348  
   349  		// Check that the node has been updated
   350  		state := s.Agent.server.State()
   351  		out, err := state.NodeByID(nil, node.ID)
   352  		require.Nil(err)
   353  		require.Equal(structs.NodeSchedulingIneligible, out.SchedulingEligibility)
   354  
   355  		// Make the HTTP request to set something invalid
   356  		eligibilityReq.Eligibility = "foo"
   357  		buf = encodeReq(eligibilityReq)
   358  		req, err = http.NewRequest("POST", "/v1/node/"+node.ID+"/eligibility", buf)
   359  		require.Nil(err)
   360  		respW = httptest.NewRecorder()
   361  
   362  		// Make the request
   363  		_, err = s.Server.NodeSpecificRequest(respW, req)
   364  		require.NotNil(err)
   365  		require.Contains(err.Error(), "invalid")
   366  	})
   367  }
   368  
   369  func TestHTTP_NodePurge(t *testing.T) {
   370  	t.Parallel()
   371  	httpTest(t, nil, func(s *TestAgent) {
   372  		// Create the node
   373  		node := mock.Node()
   374  		args := structs.NodeRegisterRequest{
   375  			Node:         node,
   376  			WriteRequest: structs.WriteRequest{Region: "global"},
   377  		}
   378  		var resp structs.NodeUpdateResponse
   379  		if err := s.Agent.RPC("Node.Register", &args, &resp); err != nil {
   380  			t.Fatalf("err: %v", err)
   381  		}
   382  
   383  		// Add some allocations to the node
   384  		state := s.Agent.server.State()
   385  		alloc1 := mock.Alloc()
   386  		alloc1.NodeID = node.ID
   387  		if err := state.UpsertJobSummary(999, mock.JobSummary(alloc1.JobID)); err != nil {
   388  			t.Fatal(err)
   389  		}
   390  		err := state.UpsertAllocs(1000, []*structs.Allocation{alloc1})
   391  		if err != nil {
   392  			t.Fatalf("err: %v", err)
   393  		}
   394  
   395  		// Make the HTTP request to purge it
   396  		req, err := http.NewRequest("POST", "/v1/node/"+node.ID+"/purge", nil)
   397  		if err != nil {
   398  			t.Fatalf("err: %v", err)
   399  		}
   400  		respW := httptest.NewRecorder()
   401  
   402  		// Make the request
   403  		obj, err := s.Server.NodeSpecificRequest(respW, req)
   404  		if err != nil {
   405  			t.Fatalf("err: %v", err)
   406  		}
   407  
   408  		// Check for the index
   409  		if respW.HeaderMap.Get("X-Nomad-Index") == "" {
   410  			t.Fatalf("missing index")
   411  		}
   412  
   413  		// Check the response
   414  		upd := obj.(structs.NodeUpdateResponse)
   415  		if len(upd.EvalIDs) == 0 {
   416  			t.Fatalf("bad: %v", upd)
   417  		}
   418  
   419  		// Ensure that the node is not present anymore
   420  		args1 := structs.NodeSpecificRequest{
   421  			NodeID:       node.ID,
   422  			QueryOptions: structs.QueryOptions{Region: "global"},
   423  		}
   424  		var resp1 structs.SingleNodeResponse
   425  		if err := s.Agent.RPC("Node.GetNode", &args1, &resp1); err != nil {
   426  			t.Fatalf("err: %v", err)
   427  		}
   428  		if resp1.Node != nil {
   429  			t.Fatalf("node still exists after purging: %#v", resp1.Node)
   430  		}
   431  	})
   432  }
   433  
   434  func TestHTTP_NodeQuery(t *testing.T) {
   435  	t.Parallel()
   436  	httpTest(t, nil, func(s *TestAgent) {
   437  		// Create the job
   438  		node := mock.Node()
   439  		args := structs.NodeRegisterRequest{
   440  			Node:         node,
   441  			WriteRequest: structs.WriteRequest{Region: "global"},
   442  		}
   443  		var resp structs.NodeUpdateResponse
   444  		if err := s.Agent.RPC("Node.Register", &args, &resp); err != nil {
   445  			t.Fatalf("err: %v", err)
   446  		}
   447  
   448  		// Make the HTTP request
   449  		req, err := http.NewRequest("GET", "/v1/node/"+node.ID, nil)
   450  		if err != nil {
   451  			t.Fatalf("err: %v", err)
   452  		}
   453  		respW := httptest.NewRecorder()
   454  
   455  		// Make the request
   456  		obj, err := s.Server.NodeSpecificRequest(respW, req)
   457  		if err != nil {
   458  			t.Fatalf("err: %v", err)
   459  		}
   460  
   461  		// Check for the index
   462  		if respW.HeaderMap.Get("X-Nomad-Index") == "" {
   463  			t.Fatalf("missing index")
   464  		}
   465  		if respW.HeaderMap.Get("X-Nomad-KnownLeader") != "true" {
   466  			t.Fatalf("missing known leader")
   467  		}
   468  		if respW.HeaderMap.Get("X-Nomad-LastContact") == "" {
   469  			t.Fatalf("missing last contact")
   470  		}
   471  
   472  		// Check the node
   473  		n := obj.(*structs.Node)
   474  		if n.ID != node.ID {
   475  			t.Fatalf("bad: %#v", n)
   476  		}
   477  		if len(n.Events) < 1 {
   478  			t.Fatalf("Expected node registration event to be populated: %#v", n)
   479  		}
   480  		if n.Events[0].Message != "Node registered" {
   481  			t.Fatalf("Expected node registration event to be first node event: %#v", n)
   482  		}
   483  	})
   484  }