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