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 }