github.com/djenriquez/nomad-1@v0.8.1/command/agent/fs_endpoint_test.go (about)

     1  package agent
     2  
     3  import (
     4  	"encoding/base64"
     5  	"fmt"
     6  	"io"
     7  	"io/ioutil"
     8  	"net/http"
     9  	"net/http/httptest"
    10  	"strings"
    11  	"testing"
    12  	"time"
    13  
    14  	cstructs "github.com/hashicorp/nomad/client/structs"
    15  	"github.com/hashicorp/nomad/nomad/mock"
    16  	"github.com/hashicorp/nomad/nomad/structs"
    17  	"github.com/hashicorp/nomad/testutil"
    18  	"github.com/stretchr/testify/require"
    19  )
    20  
    21  const (
    22  	defaultLoggerMockDriverStdout = "Hello from the other side"
    23  )
    24  
    25  var (
    26  	defaultLoggerMockDriver = map[string]interface{}{
    27  		"run_for":       "2s",
    28  		"stdout_string": defaultLoggerMockDriverStdout,
    29  	}
    30  )
    31  
    32  type clientAllocWaiter int
    33  
    34  const (
    35  	noWaitClientAlloc clientAllocWaiter = iota
    36  	runningClientAlloc
    37  	terminalClientAlloc
    38  )
    39  
    40  func addAllocToClient(agent *TestAgent, alloc *structs.Allocation, wait clientAllocWaiter) {
    41  	require := require.New(agent.T)
    42  
    43  	// Wait for the client to connect
    44  	testutil.WaitForResult(func() (bool, error) {
    45  		node, err := agent.server.State().NodeByID(nil, agent.client.NodeID())
    46  		if err != nil {
    47  			return false, err
    48  		}
    49  		if node == nil {
    50  			return false, fmt.Errorf("unknown node")
    51  		}
    52  
    53  		return node.Status == structs.NodeStatusReady, fmt.Errorf("bad node status")
    54  	}, func(err error) {
    55  		agent.T.Fatal(err)
    56  	})
    57  
    58  	// Upsert the allocation
    59  	state := agent.server.State()
    60  	require.Nil(state.UpsertJob(999, alloc.Job))
    61  	require.Nil(state.UpsertAllocs(1003, []*structs.Allocation{alloc}))
    62  
    63  	if wait == noWaitClientAlloc {
    64  		return
    65  	}
    66  
    67  	// Wait for the client to run the allocation
    68  	testutil.WaitForResult(func() (bool, error) {
    69  		alloc, err := state.AllocByID(nil, alloc.ID)
    70  		if err != nil {
    71  			return false, err
    72  		}
    73  		if alloc == nil {
    74  			return false, fmt.Errorf("unknown alloc")
    75  		}
    76  
    77  		expectation := alloc.ClientStatus == structs.AllocClientStatusComplete ||
    78  			alloc.ClientStatus == structs.AllocClientStatusFailed
    79  		if wait == runningClientAlloc {
    80  			expectation = expectation || alloc.ClientStatus == structs.AllocClientStatusRunning
    81  		}
    82  
    83  		if !expectation {
    84  			return false, fmt.Errorf("alloc client status: %v", alloc.ClientStatus)
    85  		}
    86  
    87  		return true, nil
    88  	}, func(err error) {
    89  		agent.T.Fatal(err)
    90  	})
    91  }
    92  
    93  // mockFSAlloc returns a suitable mock alloc for testing the fs system. If
    94  // config isn't provided, the defaultLoggerMockDriver config is used.
    95  func mockFSAlloc(nodeID string, config map[string]interface{}) *structs.Allocation {
    96  	a := mock.Alloc()
    97  	a.NodeID = nodeID
    98  	a.Job.Type = structs.JobTypeBatch
    99  	a.Job.TaskGroups[0].Count = 1
   100  	a.Job.TaskGroups[0].Tasks[0].Driver = "mock_driver"
   101  
   102  	if config != nil {
   103  		a.Job.TaskGroups[0].Tasks[0].Config = config
   104  	} else {
   105  		a.Job.TaskGroups[0].Tasks[0].Config = defaultLoggerMockDriver
   106  	}
   107  
   108  	return a
   109  }
   110  
   111  func TestHTTP_FS_List_MissingParams(t *testing.T) {
   112  	t.Parallel()
   113  	require := require.New(t)
   114  	httpTest(t, nil, func(s *TestAgent) {
   115  		req, err := http.NewRequest("GET", "/v1/client/fs/ls/", nil)
   116  		require.Nil(err)
   117  		respW := httptest.NewRecorder()
   118  		_, err = s.Server.DirectoryListRequest(respW, req)
   119  		require.EqualError(err, allocIDNotPresentErr.Error())
   120  	})
   121  }
   122  
   123  func TestHTTP_FS_Stat_MissingParams(t *testing.T) {
   124  	t.Parallel()
   125  	require := require.New(t)
   126  	httpTest(t, nil, func(s *TestAgent) {
   127  		req, err := http.NewRequest("GET", "/v1/client/fs/stat/", nil)
   128  		require.Nil(err)
   129  		respW := httptest.NewRecorder()
   130  
   131  		_, err = s.Server.FileStatRequest(respW, req)
   132  		require.EqualError(err, allocIDNotPresentErr.Error())
   133  
   134  		req, err = http.NewRequest("GET", "/v1/client/fs/stat/foo", nil)
   135  		require.Nil(err)
   136  		respW = httptest.NewRecorder()
   137  
   138  		_, err = s.Server.FileStatRequest(respW, req)
   139  		require.EqualError(err, fileNameNotPresentErr.Error())
   140  	})
   141  }
   142  
   143  func TestHTTP_FS_ReadAt_MissingParams(t *testing.T) {
   144  	t.Parallel()
   145  	require := require.New(t)
   146  	httpTest(t, nil, func(s *TestAgent) {
   147  		req, err := http.NewRequest("GET", "/v1/client/fs/readat/", nil)
   148  		require.Nil(err)
   149  		respW := httptest.NewRecorder()
   150  
   151  		_, err = s.Server.FileReadAtRequest(respW, req)
   152  		require.NotNil(err)
   153  
   154  		req, err = http.NewRequest("GET", "/v1/client/fs/readat/foo", nil)
   155  		require.Nil(err)
   156  		respW = httptest.NewRecorder()
   157  
   158  		_, err = s.Server.FileReadAtRequest(respW, req)
   159  		require.NotNil(err)
   160  
   161  		req, err = http.NewRequest("GET", "/v1/client/fs/readat/foo?path=/path/to/file", nil)
   162  		require.Nil(err)
   163  		respW = httptest.NewRecorder()
   164  
   165  		_, err = s.Server.FileReadAtRequest(respW, req)
   166  		require.NotNil(err)
   167  	})
   168  }
   169  
   170  func TestHTTP_FS_Cat_MissingParams(t *testing.T) {
   171  	t.Parallel()
   172  	require := require.New(t)
   173  	httpTest(t, nil, func(s *TestAgent) {
   174  		req, err := http.NewRequest("GET", "/v1/client/fs/cat/", nil)
   175  		require.Nil(err)
   176  		respW := httptest.NewRecorder()
   177  
   178  		_, err = s.Server.FileCatRequest(respW, req)
   179  		require.EqualError(err, allocIDNotPresentErr.Error())
   180  
   181  		req, err = http.NewRequest("GET", "/v1/client/fs/stat/foo", nil)
   182  		require.Nil(err)
   183  		respW = httptest.NewRecorder()
   184  
   185  		_, err = s.Server.FileCatRequest(respW, req)
   186  		require.EqualError(err, fileNameNotPresentErr.Error())
   187  	})
   188  }
   189  
   190  func TestHTTP_FS_Stream_MissingParams(t *testing.T) {
   191  	t.Parallel()
   192  	require := require.New(t)
   193  	httpTest(t, nil, func(s *TestAgent) {
   194  		req, err := http.NewRequest("GET", "/v1/client/fs/stream/", nil)
   195  		require.Nil(err)
   196  		respW := httptest.NewRecorder()
   197  
   198  		_, err = s.Server.Stream(respW, req)
   199  		require.EqualError(err, allocIDNotPresentErr.Error())
   200  
   201  		req, err = http.NewRequest("GET", "/v1/client/fs/stream/foo", nil)
   202  		require.Nil(err)
   203  		respW = httptest.NewRecorder()
   204  
   205  		_, err = s.Server.Stream(respW, req)
   206  		require.EqualError(err, fileNameNotPresentErr.Error())
   207  
   208  		req, err = http.NewRequest("GET", "/v1/client/fs/stream/foo?path=/path/to/file", nil)
   209  		require.Nil(err)
   210  		respW = httptest.NewRecorder()
   211  
   212  		_, err = s.Server.Stream(respW, req)
   213  		require.Nil(err)
   214  	})
   215  }
   216  
   217  func TestHTTP_FS_Logs_MissingParams(t *testing.T) {
   218  	t.Parallel()
   219  	require := require.New(t)
   220  	httpTest(t, nil, func(s *TestAgent) {
   221  		req, err := http.NewRequest("GET", "/v1/client/fs/logs/", nil)
   222  		require.Nil(err)
   223  		respW := httptest.NewRecorder()
   224  
   225  		_, err = s.Server.Logs(respW, req)
   226  		require.EqualError(err, allocIDNotPresentErr.Error())
   227  
   228  		req, err = http.NewRequest("GET", "/v1/client/fs/logs/foo", nil)
   229  		require.Nil(err)
   230  		respW = httptest.NewRecorder()
   231  
   232  		_, err = s.Server.Logs(respW, req)
   233  		require.EqualError(err, taskNotPresentErr.Error())
   234  
   235  		req, err = http.NewRequest("GET", "/v1/client/fs/logs/foo?task=foo", nil)
   236  		require.Nil(err)
   237  		respW = httptest.NewRecorder()
   238  
   239  		_, err = s.Server.Logs(respW, req)
   240  		require.EqualError(err, logTypeNotPresentErr.Error())
   241  
   242  		req, err = http.NewRequest("GET", "/v1/client/fs/logs/foo?task=foo&type=stdout", nil)
   243  		require.Nil(err)
   244  		respW = httptest.NewRecorder()
   245  
   246  		_, err = s.Server.Logs(respW, req)
   247  		require.Nil(err)
   248  	})
   249  }
   250  
   251  func TestHTTP_FS_List(t *testing.T) {
   252  	t.Parallel()
   253  	require := require.New(t)
   254  	httpTest(t, nil, func(s *TestAgent) {
   255  		a := mockFSAlloc(s.client.NodeID(), nil)
   256  		addAllocToClient(s, a, terminalClientAlloc)
   257  
   258  		req, err := http.NewRequest("GET", "/v1/client/fs/ls/"+a.ID, nil)
   259  		require.Nil(err)
   260  		respW := httptest.NewRecorder()
   261  		raw, err := s.Server.DirectoryListRequest(respW, req)
   262  		require.Nil(err)
   263  
   264  		files, ok := raw.([]*cstructs.AllocFileInfo)
   265  		require.True(ok)
   266  		require.NotEmpty(files)
   267  		require.True(files[0].IsDir)
   268  	})
   269  }
   270  
   271  func TestHTTP_FS_Stat(t *testing.T) {
   272  	t.Parallel()
   273  	require := require.New(t)
   274  	httpTest(t, nil, func(s *TestAgent) {
   275  		a := mockFSAlloc(s.client.NodeID(), nil)
   276  		addAllocToClient(s, a, terminalClientAlloc)
   277  
   278  		path := fmt.Sprintf("/v1/client/fs/stat/%s?path=alloc/", a.ID)
   279  		req, err := http.NewRequest("GET", path, nil)
   280  		require.Nil(err)
   281  		respW := httptest.NewRecorder()
   282  		raw, err := s.Server.FileStatRequest(respW, req)
   283  		require.Nil(err)
   284  
   285  		info, ok := raw.(*cstructs.AllocFileInfo)
   286  		require.True(ok)
   287  		require.NotNil(info)
   288  		require.True(info.IsDir)
   289  	})
   290  }
   291  
   292  func TestHTTP_FS_ReadAt(t *testing.T) {
   293  	t.Parallel()
   294  	require := require.New(t)
   295  	httpTest(t, nil, func(s *TestAgent) {
   296  		a := mockFSAlloc(s.client.NodeID(), nil)
   297  		addAllocToClient(s, a, terminalClientAlloc)
   298  
   299  		offset := 1
   300  		limit := 3
   301  		expectation := defaultLoggerMockDriverStdout[offset : offset+limit]
   302  		path := fmt.Sprintf("/v1/client/fs/readat/%s?path=alloc/logs/web.stdout.0&offset=%d&limit=%d",
   303  			a.ID, offset, limit)
   304  
   305  		req, err := http.NewRequest("GET", path, nil)
   306  		require.Nil(err)
   307  		respW := httptest.NewRecorder()
   308  		_, err = s.Server.FileReadAtRequest(respW, req)
   309  		require.Nil(err)
   310  
   311  		output, err := ioutil.ReadAll(respW.Result().Body)
   312  		require.Nil(err)
   313  		require.EqualValues(expectation, output)
   314  	})
   315  }
   316  
   317  func TestHTTP_FS_Cat(t *testing.T) {
   318  	t.Parallel()
   319  	require := require.New(t)
   320  	httpTest(t, nil, func(s *TestAgent) {
   321  		a := mockFSAlloc(s.client.NodeID(), nil)
   322  		addAllocToClient(s, a, terminalClientAlloc)
   323  
   324  		path := fmt.Sprintf("/v1/client/fs/cat/%s?path=alloc/logs/web.stdout.0", a.ID)
   325  
   326  		req, err := http.NewRequest("GET", path, nil)
   327  		require.Nil(err)
   328  		respW := httptest.NewRecorder()
   329  		_, err = s.Server.FileCatRequest(respW, req)
   330  		require.Nil(err)
   331  
   332  		output, err := ioutil.ReadAll(respW.Result().Body)
   333  		require.Nil(err)
   334  		require.EqualValues(defaultLoggerMockDriverStdout, output)
   335  	})
   336  }
   337  
   338  func TestHTTP_FS_Stream(t *testing.T) {
   339  	t.Parallel()
   340  	require := require.New(t)
   341  	httpTest(t, nil, func(s *TestAgent) {
   342  		a := mockFSAlloc(s.client.NodeID(), nil)
   343  		addAllocToClient(s, a, terminalClientAlloc)
   344  
   345  		offset := 4
   346  		expectation := base64.StdEncoding.EncodeToString(
   347  			[]byte(defaultLoggerMockDriverStdout[len(defaultLoggerMockDriverStdout)-offset:]))
   348  		path := fmt.Sprintf("/v1/client/fs/stream/%s?path=alloc/logs/web.stdout.0&offset=%d&origin=end",
   349  			a.ID, offset)
   350  
   351  		p, _ := io.Pipe()
   352  
   353  		req, err := http.NewRequest("GET", path, p)
   354  		require.Nil(err)
   355  		respW := httptest.NewRecorder()
   356  		doneCh := make(chan struct{})
   357  		go func() {
   358  			_, err = s.Server.Stream(respW, req)
   359  			require.Nil(err)
   360  			close(doneCh)
   361  		}()
   362  
   363  		out := ""
   364  		testutil.WaitForResult(func() (bool, error) {
   365  			output, err := ioutil.ReadAll(respW.Body)
   366  			if err != nil {
   367  				return false, err
   368  			}
   369  
   370  			out += string(output)
   371  			return strings.Contains(out, expectation), fmt.Errorf("%q doesn't contain %q", out, expectation)
   372  		}, func(err error) {
   373  			t.Fatal(err)
   374  		})
   375  
   376  		select {
   377  		case <-doneCh:
   378  			t.Fatal("shouldn't close")
   379  		case <-time.After(1 * time.Second):
   380  		}
   381  
   382  		p.Close()
   383  	})
   384  }
   385  
   386  func TestHTTP_FS_Logs(t *testing.T) {
   387  	t.Parallel()
   388  	require := require.New(t)
   389  	httpTest(t, nil, func(s *TestAgent) {
   390  		a := mockFSAlloc(s.client.NodeID(), nil)
   391  		addAllocToClient(s, a, terminalClientAlloc)
   392  
   393  		offset := 4
   394  		expectation := defaultLoggerMockDriverStdout[len(defaultLoggerMockDriverStdout)-offset:]
   395  		path := fmt.Sprintf("/v1/client/fs/logs/%s?type=stdout&task=web&offset=%d&origin=end&plain=true",
   396  			a.ID, offset)
   397  
   398  		p, _ := io.Pipe()
   399  		req, err := http.NewRequest("GET", path, p)
   400  		require.Nil(err)
   401  		respW := httptest.NewRecorder()
   402  		go func() {
   403  			_, err = s.Server.Logs(respW, req)
   404  			require.Nil(err)
   405  		}()
   406  
   407  		out := ""
   408  		testutil.WaitForResult(func() (bool, error) {
   409  			output, err := ioutil.ReadAll(respW.Body)
   410  			if err != nil {
   411  				return false, err
   412  			}
   413  
   414  			out += string(output)
   415  			return out == expectation, fmt.Errorf("%q != %q", out, expectation)
   416  		}, func(err error) {
   417  			t.Fatal(err)
   418  		})
   419  
   420  		p.Close()
   421  	})
   422  }
   423  
   424  func TestHTTP_FS_Logs_Follow(t *testing.T) {
   425  	t.Parallel()
   426  	require := require.New(t)
   427  	httpTest(t, nil, func(s *TestAgent) {
   428  		a := mockFSAlloc(s.client.NodeID(), nil)
   429  		addAllocToClient(s, a, terminalClientAlloc)
   430  
   431  		offset := 4
   432  		expectation := defaultLoggerMockDriverStdout[len(defaultLoggerMockDriverStdout)-offset:]
   433  		path := fmt.Sprintf("/v1/client/fs/logs/%s?type=stdout&task=web&offset=%d&origin=end&plain=true&follow=true",
   434  			a.ID, offset)
   435  
   436  		p, _ := io.Pipe()
   437  		req, err := http.NewRequest("GET", path, p)
   438  		require.Nil(err)
   439  		respW := httptest.NewRecorder()
   440  		errCh := make(chan error)
   441  		go func() {
   442  			_, err := s.Server.Logs(respW, req)
   443  			errCh <- err
   444  		}()
   445  
   446  		out := ""
   447  		testutil.WaitForResult(func() (bool, error) {
   448  			output, err := ioutil.ReadAll(respW.Body)
   449  			if err != nil {
   450  				return false, err
   451  			}
   452  
   453  			out += string(output)
   454  			return out == expectation, fmt.Errorf("%q != %q", out, expectation)
   455  		}, func(err error) {
   456  			t.Fatal(err)
   457  		})
   458  
   459  		select {
   460  		case err := <-errCh:
   461  			t.Fatalf("shouldn't exit: %v", err)
   462  		case <-time.After(1 * time.Second):
   463  		}
   464  
   465  		p.Close()
   466  	})
   467  }