Add ability to copy files to and delete files from runner
This commit is contained in:
@ -1,6 +1,13 @@
|
||||
package tests
|
||||
|
||||
import "errors"
|
||||
|
||||
const (
|
||||
NonExistingId = "n0n-3x1st1ng-1d"
|
||||
DefaultFileName = "test.txt"
|
||||
DefaultFileContent = "Hello, Codemoon!"
|
||||
DefaultDirectoryName = "test/"
|
||||
FileNameWithAbsolutePath = "/test.txt"
|
||||
DefaultEnvironmentIdAsInteger = 0
|
||||
AnotherEnvironmentIdAsInteger = 42
|
||||
DefaultJobId = "s0m3-j0b-1d"
|
||||
@ -8,4 +15,9 @@ const (
|
||||
DefaultRunnerId = "s0m3-r4nd0m-1d"
|
||||
AnotherRunnerId = "4n0th3r-runn3r-1d"
|
||||
DefaultExecutionId = "s0m3-3x3cu710n-1d"
|
||||
DefaultMockId = "m0ck-1d"
|
||||
)
|
||||
|
||||
var (
|
||||
DefaultError = errors.New("an error occured")
|
||||
)
|
@ -21,7 +21,7 @@ type E2ETestSuite struct {
|
||||
suite.Suite
|
||||
}
|
||||
|
||||
func (suite *E2ETestSuite) SetupTest() {
|
||||
func (s *E2ETestSuite) SetupTest() {
|
||||
// Waiting one second before each test allows Nomad to rescale after tests requested runners.
|
||||
<-time.After(time.Second)
|
||||
}
|
||||
|
@ -3,13 +3,13 @@ package e2e
|
||||
import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"gitlab.hpi.de/codeocean/codemoon/poseidon/api"
|
||||
"gitlab.hpi.de/codeocean/codemoon/poseidon/tests/e2e/helpers"
|
||||
"gitlab.hpi.de/codeocean/codemoon/poseidon/tests/helpers"
|
||||
"net/http"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestHealthRoute(t *testing.T) {
|
||||
resp, err := http.Get(helpers.BuildURL(api.RouteBase, api.RouteHealth))
|
||||
resp, err := http.Get(helpers.BuildURL(api.BasePath, api.HealthPath))
|
||||
if assert.NoError(t, err) {
|
||||
assert.Equal(t, http.StatusNoContent, resp.StatusCode, "The response code should be NoContent")
|
||||
}
|
||||
|
@ -1,67 +1,170 @@
|
||||
package e2e
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"fmt"
|
||||
"github.com/gorilla/websocket"
|
||||
"gitlab.hpi.de/codeocean/codemoon/poseidon/api"
|
||||
"gitlab.hpi.de/codeocean/codemoon/poseidon/api/dto"
|
||||
"gitlab.hpi.de/codeocean/codemoon/poseidon/tests/e2e/helpers"
|
||||
"gitlab.hpi.de/codeocean/codemoon/poseidon/runner"
|
||||
"gitlab.hpi.de/codeocean/codemoon/poseidon/tests"
|
||||
"gitlab.hpi.de/codeocean/codemoon/poseidon/tests/helpers"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func (suite *E2ETestSuite) TestProvideRunnerRoute() {
|
||||
func (s *E2ETestSuite) TestProvideRunnerRoute() {
|
||||
runnerRequestString, _ := json.Marshal(dto.RunnerRequest{})
|
||||
reader := strings.NewReader(string(runnerRequestString))
|
||||
resp, err := http.Post(helpers.BuildURL(api.RouteBase, api.RouteRunners), "application/json", reader)
|
||||
suite.NoError(err)
|
||||
suite.Equal(http.StatusOK, resp.StatusCode, "The response code should be ok")
|
||||
resp, err := http.Post(helpers.BuildURL(api.BasePath, api.RunnersPath), "application/json", reader)
|
||||
s.NoError(err)
|
||||
s.Equal(http.StatusOK, resp.StatusCode, "The response code should be ok")
|
||||
|
||||
runnerResponse := new(dto.RunnerResponse)
|
||||
err = json.NewDecoder(resp.Body).Decode(runnerResponse)
|
||||
suite.NoError(err)
|
||||
s.NoError(err)
|
||||
|
||||
suite.True(runnerResponse.Id != "", "The response contains a runner id")
|
||||
s.True(runnerResponse.Id != "", "The response contains a runner id")
|
||||
}
|
||||
|
||||
func newRunnerId(t *testing.T) string {
|
||||
runnerRequestString, _ := json.Marshal(dto.RunnerRequest{})
|
||||
// ProvideRunner creates a runner with the given RunnerRequest via an external request.
|
||||
// It needs a running Poseidon instance to work.
|
||||
func ProvideRunner(request *dto.RunnerRequest) (string, error) {
|
||||
url := helpers.BuildURL(api.BasePath, api.RunnersPath)
|
||||
runnerRequestString, _ := json.Marshal(request)
|
||||
reader := strings.NewReader(string(runnerRequestString))
|
||||
resp, err := http.Post(helpers.BuildURL(api.RouteBase, api.RouteRunners), "application/json", reader)
|
||||
assert.NoError(t, err)
|
||||
resp, err := http.Post(url, "application/json", reader)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return "", fmt.Errorf("expected response code 200 when getting runner, got %v", resp.StatusCode)
|
||||
}
|
||||
runnerResponse := new(dto.RunnerResponse)
|
||||
_ = json.NewDecoder(resp.Body).Decode(runnerResponse)
|
||||
return runnerResponse.Id
|
||||
err = json.NewDecoder(resp.Body).Decode(runnerResponse)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return runnerResponse.Id, nil
|
||||
}
|
||||
|
||||
func (suite *E2ETestSuite) TestDeleteRunnerRoute() {
|
||||
runnerId := newRunnerId(suite.T())
|
||||
suite.NotEqual("", runnerId)
|
||||
func (s *E2ETestSuite) TestDeleteRunnerRoute() {
|
||||
runnerId, err := ProvideRunner(&dto.RunnerRequest{})
|
||||
s.NoError(err)
|
||||
|
||||
suite.Run("Deleting the runner returns NoContent", func() {
|
||||
resp, err := httpDelete(helpers.BuildURL(api.RouteBase, api.RouteRunners, "/", runnerId), nil)
|
||||
suite.NoError(err)
|
||||
suite.Equal(http.StatusNoContent, resp.StatusCode)
|
||||
s.Run("Deleting the runner returns NoContent", func() {
|
||||
resp, err := helpers.HttpDelete(helpers.BuildURL(api.BasePath, api.RunnersPath, runnerId), nil)
|
||||
s.NoError(err)
|
||||
s.Equal(http.StatusNoContent, resp.StatusCode)
|
||||
})
|
||||
|
||||
suite.Run("Deleting it again returns NotFound", func() {
|
||||
resp, err := httpDelete(helpers.BuildURL(api.RouteBase, api.RouteRunners, "/", runnerId), nil)
|
||||
suite.NoError(err)
|
||||
suite.Equal(http.StatusNotFound, resp.StatusCode)
|
||||
s.Run("Deleting it again returns NotFound", func() {
|
||||
resp, err := helpers.HttpDelete(helpers.BuildURL(api.BasePath, api.RunnersPath, runnerId), nil)
|
||||
s.NoError(err)
|
||||
s.Equal(http.StatusNotFound, resp.StatusCode)
|
||||
})
|
||||
|
||||
suite.Run("Deleting non-existing runner returns NotFound", func() {
|
||||
resp, err := httpDelete(helpers.BuildURL(api.RouteBase, api.RouteRunners, "/", "n0n-3x1st1ng-1d"), nil)
|
||||
suite.NoError(err)
|
||||
suite.Equal(http.StatusNotFound, resp.StatusCode)
|
||||
s.Run("Deleting non-existing runner returns NotFound", func() {
|
||||
resp, err := helpers.HttpDelete(helpers.BuildURL(api.BasePath, api.RunnersPath, tests.NonExistingId), nil)
|
||||
s.NoError(err)
|
||||
s.Equal(http.StatusNotFound, resp.StatusCode)
|
||||
})
|
||||
}
|
||||
|
||||
// HttpDelete sends a Delete Http Request with body to the passed url.
|
||||
func httpDelete(url string, body io.Reader) (response *http.Response, err error) {
|
||||
req, _ := http.NewRequest(http.MethodDelete, url, body)
|
||||
client := &http.Client{}
|
||||
return client.Do(req)
|
||||
func (s *E2ETestSuite) TestCopyFilesRoute() {
|
||||
runnerId, err := ProvideRunner(&dto.RunnerRequest{})
|
||||
s.NoError(err)
|
||||
copyFilesRequestByteString, _ := json.Marshal(&dto.UpdateFileSystemRequest{
|
||||
Copy: []dto.File{{Path: tests.DefaultFileName, Content: []byte(tests.DefaultFileContent)}},
|
||||
})
|
||||
sendCopyRequest := func(reader io.Reader) (*http.Response, error) {
|
||||
return helpers.HttpPatch(helpers.BuildURL(api.BasePath, api.RunnersPath, runnerId, api.UpdateFileSystemPath), "application/json", reader)
|
||||
}
|
||||
|
||||
s.Run("File copy with valid payload succeeds", func() {
|
||||
resp, err := sendCopyRequest(bytes.NewReader(copyFilesRequestByteString))
|
||||
s.NoError(err)
|
||||
s.Equal(http.StatusNoContent, resp.StatusCode)
|
||||
|
||||
s.Run("File content can be printed on runner", func() {
|
||||
s.Equal(tests.DefaultFileContent, s.ContentOfFileOnRunner(runnerId, tests.DefaultFileName))
|
||||
})
|
||||
})
|
||||
|
||||
s.Run("File deletion request deletes file on runner", func() {
|
||||
copyFilesRequestByteString, _ := json.Marshal(&dto.UpdateFileSystemRequest{
|
||||
Delete: []dto.FilePath{tests.DefaultFileName},
|
||||
})
|
||||
|
||||
resp, err := sendCopyRequest(bytes.NewReader(copyFilesRequestByteString))
|
||||
s.NoError(err)
|
||||
s.Equal(http.StatusNoContent, resp.StatusCode)
|
||||
|
||||
s.Run("File content can no longer be printed", func() {
|
||||
s.Contains(s.ContentOfFileOnRunner(runnerId, tests.DefaultFileName), "No such file or directory")
|
||||
})
|
||||
})
|
||||
|
||||
s.Run("File copy happens after file deletion", func() {
|
||||
copyFilesRequestByteString, _ := json.Marshal(&dto.UpdateFileSystemRequest{
|
||||
Delete: []dto.FilePath{tests.DefaultFileName},
|
||||
Copy: []dto.File{{Path: tests.DefaultFileName, Content: []byte(tests.DefaultFileContent)}},
|
||||
})
|
||||
|
||||
resp, err := sendCopyRequest(bytes.NewReader(copyFilesRequestByteString))
|
||||
s.NoError(err)
|
||||
s.Equal(http.StatusNoContent, resp.StatusCode)
|
||||
|
||||
s.Run("File content can be printed on runner", func() {
|
||||
s.Equal(tests.DefaultFileContent, s.ContentOfFileOnRunner(runnerId, tests.DefaultFileName))
|
||||
})
|
||||
})
|
||||
|
||||
s.Run("If one file produces permission denied error, others are still copied", func() {
|
||||
newFileContent := []byte("New content")
|
||||
copyFilesRequestByteString, _ := json.Marshal(&dto.UpdateFileSystemRequest{
|
||||
Copy: []dto.File{
|
||||
{Path: "/dev/sda", Content: []byte(tests.DefaultFileContent)},
|
||||
{Path: tests.DefaultFileName, Content: newFileContent},
|
||||
},
|
||||
})
|
||||
|
||||
resp, err := sendCopyRequest(bytes.NewReader(copyFilesRequestByteString))
|
||||
s.NoError(err)
|
||||
s.Equal(http.StatusInternalServerError, resp.StatusCode)
|
||||
internalServerError := new(dto.InternalServerError)
|
||||
err = json.NewDecoder(resp.Body).Decode(internalServerError)
|
||||
s.NoError(err)
|
||||
s.Contains(internalServerError.Message, "Cannot open: Permission denied")
|
||||
|
||||
s.Run("File content can be printed on runner", func() {
|
||||
s.Equal(string(newFileContent), s.ContentOfFileOnRunner(runnerId, tests.DefaultFileName))
|
||||
})
|
||||
})
|
||||
|
||||
s.Run("File copy with invalid payload returns bad request", func() {
|
||||
resp, err := helpers.HttpPatch(helpers.BuildURL(api.BasePath, api.RunnersPath, runnerId, api.UpdateFileSystemPath), "text/html", strings.NewReader(""))
|
||||
s.NoError(err)
|
||||
s.Equal(http.StatusBadRequest, resp.StatusCode)
|
||||
})
|
||||
|
||||
s.Run("Copying to non-existing runner returns NotFound", func() {
|
||||
resp, err := helpers.HttpPatch(helpers.BuildURL(api.BasePath, api.RunnersPath, tests.NonExistingId, api.UpdateFileSystemPath), "application/json", bytes.NewReader(copyFilesRequestByteString))
|
||||
s.NoError(err)
|
||||
s.Equal(http.StatusNotFound, resp.StatusCode)
|
||||
})
|
||||
}
|
||||
|
||||
func (s *E2ETestSuite) ContentOfFileOnRunner(runnerId string, filename string) string {
|
||||
webSocketURL, _ := ProvideWebSocketURL(&s.Suite, runnerId, &dto.ExecutionRequest{Command: fmt.Sprintf("cat %s/%s", runner.FileCopyBasePath, filename)})
|
||||
connection, _ := ConnectToWebSocket(webSocketURL)
|
||||
|
||||
messages, err := helpers.ReceiveAllWebSocketMessages(connection)
|
||||
s.Require().Error(err)
|
||||
s.Equal(&websocket.CloseError{Code: websocket.CloseNormalClosure}, err)
|
||||
|
||||
stdout, _, _ := helpers.WebSocketOutputMessages(messages)
|
||||
return stdout
|
||||
}
|
||||
|
@ -7,25 +7,25 @@ import (
|
||||
"github.com/stretchr/testify/suite"
|
||||
"gitlab.hpi.de/codeocean/codemoon/poseidon/api"
|
||||
"gitlab.hpi.de/codeocean/codemoon/poseidon/api/dto"
|
||||
"gitlab.hpi.de/codeocean/codemoon/poseidon/tests/e2e/helpers"
|
||||
"gitlab.hpi.de/codeocean/codemoon/poseidon/tests/helpers"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func (suite *E2ETestSuite) TestExecuteCommandRoute() {
|
||||
runnerId, err := ProvideRunner(&suite.Suite, &dto.RunnerRequest{})
|
||||
suite.Require().NoError(err)
|
||||
func (s *E2ETestSuite) TestExecuteCommandRoute() {
|
||||
runnerId, err := ProvideRunner(&dto.RunnerRequest{})
|
||||
s.Require().NoError(err)
|
||||
|
||||
webSocketURL, err := ProvideWebSocketURL(&suite.Suite, runnerId, &dto.ExecutionRequest{Command: "true"})
|
||||
suite.Require().NoError(err)
|
||||
suite.NotEqual("", webSocketURL)
|
||||
webSocketURL, err := ProvideWebSocketURL(&s.Suite, runnerId, &dto.ExecutionRequest{Command: "true"})
|
||||
s.Require().NoError(err)
|
||||
s.NotEqual("", webSocketURL)
|
||||
|
||||
var connection *websocket.Conn
|
||||
var connectionClosed bool
|
||||
|
||||
connection, err = ConnectToWebSocket(webSocketURL)
|
||||
suite.Require().NoError(err, "websocket connects")
|
||||
s.Require().NoError(err, "websocket connects")
|
||||
closeHandler := connection.CloseHandler()
|
||||
connection.SetCloseHandler(func(code int, text string) error {
|
||||
connectionClosed = true
|
||||
@ -33,119 +33,119 @@ func (suite *E2ETestSuite) TestExecuteCommandRoute() {
|
||||
})
|
||||
|
||||
startMessage, err := helpers.ReceiveNextWebSocketMessage(connection)
|
||||
suite.Require().NoError(err)
|
||||
suite.Equal(&dto.WebSocketMessage{Type: dto.WebSocketMetaStart}, startMessage)
|
||||
s.Require().NoError(err)
|
||||
s.Equal(&dto.WebSocketMessage{Type: dto.WebSocketMetaStart}, startMessage)
|
||||
|
||||
exitMessage, err := helpers.ReceiveNextWebSocketMessage(connection)
|
||||
suite.Require().NoError(err)
|
||||
suite.Equal(&dto.WebSocketMessage{Type: dto.WebSocketExit}, exitMessage)
|
||||
s.Require().NoError(err)
|
||||
s.Equal(&dto.WebSocketMessage{Type: dto.WebSocketExit}, exitMessage)
|
||||
|
||||
_, err = helpers.ReceiveAllWebSocketMessages(connection)
|
||||
suite.Require().Error(err)
|
||||
suite.Equal(&websocket.CloseError{Code: websocket.CloseNormalClosure}, err)
|
||||
s.Require().Error(err)
|
||||
s.Equal(&websocket.CloseError{Code: websocket.CloseNormalClosure}, err)
|
||||
|
||||
_, _, _ = connection.ReadMessage()
|
||||
suite.True(connectionClosed, "connection should be closed")
|
||||
s.True(connectionClosed, "connection should be closed")
|
||||
}
|
||||
|
||||
func (suite *E2ETestSuite) TestOutputToStdout() {
|
||||
connection, err := ProvideWebSocketConnection(&suite.Suite, &dto.ExecutionRequest{Command: "echo Hello World"})
|
||||
suite.Require().NoError(err)
|
||||
func (s *E2ETestSuite) TestOutputToStdout() {
|
||||
connection, err := ProvideWebSocketConnection(&s.Suite, &dto.ExecutionRequest{Command: "echo Hello World"})
|
||||
s.Require().NoError(err)
|
||||
|
||||
messages, err := helpers.ReceiveAllWebSocketMessages(connection)
|
||||
suite.Require().Error(err)
|
||||
suite.Equal(&websocket.CloseError{Code: websocket.CloseNormalClosure}, err)
|
||||
s.Require().Error(err)
|
||||
s.Equal(&websocket.CloseError{Code: websocket.CloseNormalClosure}, err)
|
||||
|
||||
suite.Require().Equal(&dto.WebSocketMessage{Type: dto.WebSocketMetaStart}, messages[0])
|
||||
suite.Require().Equal(&dto.WebSocketMessage{Type: dto.WebSocketOutputStdout, Data: "Hello World\r\n"}, messages[1])
|
||||
suite.Require().Equal(&dto.WebSocketMessage{Type: dto.WebSocketExit}, messages[2])
|
||||
s.Require().Equal(&dto.WebSocketMessage{Type: dto.WebSocketMetaStart}, messages[0])
|
||||
s.Require().Equal(&dto.WebSocketMessage{Type: dto.WebSocketOutputStdout, Data: "Hello World\r\n"}, messages[1])
|
||||
s.Require().Equal(&dto.WebSocketMessage{Type: dto.WebSocketExit}, messages[2])
|
||||
}
|
||||
|
||||
func (suite *E2ETestSuite) TestOutputToStderr() {
|
||||
suite.T().Skip("known bug causing all output to be written to stdout (even if it should be written to stderr)")
|
||||
connection, err := ProvideWebSocketConnection(&suite.Suite, &dto.ExecutionRequest{Command: "cat -invalid"})
|
||||
suite.Require().NoError(err)
|
||||
func (s *E2ETestSuite) TestOutputToStderr() {
|
||||
s.T().Skip("known bug causing all output to be written to stdout (even if it should be written to stderr)")
|
||||
connection, err := ProvideWebSocketConnection(&s.Suite, &dto.ExecutionRequest{Command: "cat -invalid"})
|
||||
s.Require().NoError(err)
|
||||
|
||||
messages, err := helpers.ReceiveAllWebSocketMessages(connection)
|
||||
suite.Require().Error(err)
|
||||
suite.Equal(&websocket.CloseError{Code: websocket.CloseNormalClosure}, err)
|
||||
s.Require().Error(err)
|
||||
s.Equal(&websocket.CloseError{Code: websocket.CloseNormalClosure}, err)
|
||||
|
||||
controlMessages := helpers.WebSocketControlMessages(messages)
|
||||
suite.Require().Equal(&dto.WebSocketMessage{Type: dto.WebSocketMetaStart}, controlMessages[0])
|
||||
suite.Require().Equal(&dto.WebSocketMessage{Type: dto.WebSocketExit}, controlMessages[1])
|
||||
s.Require().Equal(&dto.WebSocketMessage{Type: dto.WebSocketMetaStart}, controlMessages[0])
|
||||
s.Require().Equal(&dto.WebSocketMessage{Type: dto.WebSocketExit}, controlMessages[1])
|
||||
|
||||
stdout, stderr, errors := helpers.WebSocketOutputMessages(messages)
|
||||
suite.NotContains(stdout, "cat: invalid option", "Stdout should not contain the error")
|
||||
suite.Contains(stderr, "cat: invalid option", "Stderr should contain the error")
|
||||
suite.Empty(errors)
|
||||
s.NotContains(stdout, "cat: invalid option", "Stdout should not contain the error")
|
||||
s.Contains(stderr, "cat: invalid option", "Stderr should contain the error")
|
||||
s.Empty(errors)
|
||||
}
|
||||
|
||||
func (suite *E2ETestSuite) TestCommandHead() {
|
||||
func (s *E2ETestSuite) TestCommandHead() {
|
||||
hello := "Hello World!"
|
||||
connection, err := ProvideWebSocketConnection(&suite.Suite, &dto.ExecutionRequest{Command: "head -n 1"})
|
||||
suite.Require().NoError(err)
|
||||
connection, err := ProvideWebSocketConnection(&s.Suite, &dto.ExecutionRequest{Command: "head -n 1"})
|
||||
s.Require().NoError(err)
|
||||
|
||||
startMessage, err := helpers.ReceiveNextWebSocketMessage(connection)
|
||||
suite.Require().NoError(err)
|
||||
suite.Equal(dto.WebSocketMetaStart, startMessage.Type)
|
||||
s.Require().NoError(err)
|
||||
s.Equal(dto.WebSocketMetaStart, startMessage.Type)
|
||||
|
||||
err = connection.WriteMessage(websocket.TextMessage, []byte(fmt.Sprintf("%s\n", hello)))
|
||||
suite.Require().NoError(err)
|
||||
s.Require().NoError(err)
|
||||
|
||||
messages, err := helpers.ReceiveAllWebSocketMessages(connection)
|
||||
suite.Require().Error(err)
|
||||
suite.Equal(err, &websocket.CloseError{Code: websocket.CloseNormalClosure})
|
||||
s.Require().Error(err)
|
||||
s.Equal(err, &websocket.CloseError{Code: websocket.CloseNormalClosure})
|
||||
stdout, _, _ := helpers.WebSocketOutputMessages(messages)
|
||||
suite.Contains(stdout, fmt.Sprintf("%s\r\n%s\r\n", hello, hello))
|
||||
s.Contains(stdout, fmt.Sprintf("%s\r\n%s\r\n", hello, hello))
|
||||
}
|
||||
|
||||
func (suite *E2ETestSuite) TestCommandReturnsAfterTimeout() {
|
||||
connection, err := ProvideWebSocketConnection(&suite.Suite, &dto.ExecutionRequest{Command: "sleep 4", TimeLimit: 1})
|
||||
suite.Require().NoError(err)
|
||||
func (s *E2ETestSuite) TestCommandReturnsAfterTimeout() {
|
||||
connection, err := ProvideWebSocketConnection(&s.Suite, &dto.ExecutionRequest{Command: "sleep 4", TimeLimit: 1})
|
||||
s.Require().NoError(err)
|
||||
|
||||
c := make(chan bool)
|
||||
var messages []*dto.WebSocketMessage
|
||||
go func() {
|
||||
messages, err = helpers.ReceiveAllWebSocketMessages(connection)
|
||||
if !suite.Equal(&websocket.CloseError{Code: websocket.CloseNormalClosure}, err) {
|
||||
suite.T().Fail()
|
||||
if !s.Equal(&websocket.CloseError{Code: websocket.CloseNormalClosure}, err) {
|
||||
s.T().Fail()
|
||||
}
|
||||
close(c)
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-time.After(2 * time.Second):
|
||||
suite.T().Fatal("The execution should have returned by now")
|
||||
s.T().Fatal("The execution should have returned by now")
|
||||
case <-c:
|
||||
if suite.Equal(&dto.WebSocketMessage{Type: dto.WebSocketMetaTimeout}, messages[len(messages)-1]) {
|
||||
if s.Equal(&dto.WebSocketMessage{Type: dto.WebSocketMetaTimeout}, messages[len(messages)-1]) {
|
||||
return
|
||||
}
|
||||
}
|
||||
suite.T().Fail()
|
||||
s.T().Fail()
|
||||
}
|
||||
|
||||
func (suite *E2ETestSuite) TestEchoEnvironment() {
|
||||
connection, err := ProvideWebSocketConnection(&suite.Suite, &dto.ExecutionRequest{
|
||||
func (s *E2ETestSuite) TestEchoEnvironment() {
|
||||
connection, err := ProvideWebSocketConnection(&s.Suite, &dto.ExecutionRequest{
|
||||
Command: "echo $hello",
|
||||
Environment: map[string]string{"hello": "world"},
|
||||
})
|
||||
suite.Require().NoError(err)
|
||||
s.Require().NoError(err)
|
||||
|
||||
startMessage, err := helpers.ReceiveNextWebSocketMessage(connection)
|
||||
suite.Require().NoError(err)
|
||||
suite.Equal(dto.WebSocketMetaStart, startMessage.Type)
|
||||
s.Require().NoError(err)
|
||||
s.Equal(dto.WebSocketMetaStart, startMessage.Type)
|
||||
|
||||
messages, err := helpers.ReceiveAllWebSocketMessages(connection)
|
||||
suite.Require().Error(err)
|
||||
suite.Equal(err, &websocket.CloseError{Code: websocket.CloseNormalClosure})
|
||||
s.Require().Error(err)
|
||||
s.Equal(err, &websocket.CloseError{Code: websocket.CloseNormalClosure})
|
||||
stdout, _, _ := helpers.WebSocketOutputMessages(messages)
|
||||
suite.Equal("world\r\n", stdout)
|
||||
s.Equal("world\r\n", stdout)
|
||||
}
|
||||
|
||||
// ProvideWebSocketConnection establishes a client WebSocket connection to run the passed ExecutionRequest.
|
||||
// It requires a running Poseidon instance.
|
||||
func ProvideWebSocketConnection(suite *suite.Suite, request *dto.ExecutionRequest) (connection *websocket.Conn, err error) {
|
||||
runnerId, err := ProvideRunner(suite, &dto.RunnerRequest{})
|
||||
runnerId, err := ProvideRunner(&dto.RunnerRequest{})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@ -157,27 +157,10 @@ func ProvideWebSocketConnection(suite *suite.Suite, request *dto.ExecutionReques
|
||||
return
|
||||
}
|
||||
|
||||
// ProvideRunner creates a runner with the given RunnerRequest via an external request.
|
||||
// It needs a running Poseidon instance to work.
|
||||
func ProvideRunner(suite *suite.Suite, request *dto.RunnerRequest) (string, error) {
|
||||
url := helpers.BuildURL(api.RouteBase, api.RouteRunners)
|
||||
runnerRequestBytes, _ := json.Marshal(request)
|
||||
reader := strings.NewReader(string(runnerRequestBytes))
|
||||
resp, err := http.Post(url, "application/json", reader)
|
||||
suite.Require().NoError(err)
|
||||
suite.Require().Equal(http.StatusOK, resp.StatusCode)
|
||||
|
||||
runnerResponse := new(dto.RunnerResponse)
|
||||
err = json.NewDecoder(resp.Body).Decode(runnerResponse)
|
||||
suite.Require().NoError(err)
|
||||
|
||||
return runnerResponse.Id, nil
|
||||
}
|
||||
|
||||
// ProvideWebSocketURL creates a WebSocket endpoint from the ExecutionRequest via an external api request.
|
||||
// It requires a running Poseidon instance.
|
||||
func ProvideWebSocketURL(suite *suite.Suite, runnerId string, request *dto.ExecutionRequest) (string, error) {
|
||||
url := helpers.BuildURL(api.RouteBase, api.RouteRunners, "/", runnerId, api.ExecutePath)
|
||||
url := helpers.BuildURL(api.BasePath, api.RunnersPath, runnerId, api.ExecutePath)
|
||||
executionRequestBytes, _ := json.Marshal(request)
|
||||
reader := strings.NewReader(string(executionRequestBytes))
|
||||
resp, err := http.Post(url, "application/json", reader)
|
||||
|
@ -12,8 +12,8 @@ import (
|
||||
"gitlab.hpi.de/codeocean/codemoon/poseidon/api/dto"
|
||||
"gitlab.hpi.de/codeocean/codemoon/poseidon/config"
|
||||
"gitlab.hpi.de/codeocean/codemoon/poseidon/nomad"
|
||||
"gitlab.hpi.de/codeocean/codemoon/poseidon/runner"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
@ -23,8 +23,14 @@ import (
|
||||
|
||||
// BuildURL joins multiple route paths.
|
||||
func BuildURL(parts ...string) (url string) {
|
||||
parts = append([]string{config.Config.PoseidonAPIURL().String()}, parts...)
|
||||
return strings.Join(parts, "")
|
||||
url = config.Config.PoseidonAPIURL().String()
|
||||
for _, part := range parts {
|
||||
if !strings.HasPrefix(part, "/") {
|
||||
url += "/"
|
||||
}
|
||||
url += part
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// WebSocketOutputMessages extracts all stdout, stderr and error messages from the passed messages.
|
||||
@ -105,20 +111,15 @@ func StartTLSServer(t *testing.T, router *mux.Router) (server *httptest.Server,
|
||||
return
|
||||
}
|
||||
|
||||
func NewNomadAllocationWithMockedApiClient(runnerId string) (r runner.Runner, mock *nomad.ExecutorApiMock) {
|
||||
mock = &nomad.ExecutorApiMock{}
|
||||
r = runner.NewNomadAllocation(runnerId, mock)
|
||||
return
|
||||
}
|
||||
|
||||
// MockApiExecute mocks the ExecuteCommand method of an ExecutorApi to call the given method run when the command
|
||||
// corresponding to the given ExecutionRequest is called.
|
||||
func MockApiExecute(api *nomad.ExecutorApiMock, request *dto.ExecutionRequest,
|
||||
run func(runnerId string, ctx context.Context, command []string, stdin io.Reader, stdout, stderr io.Writer) (int, error)) {
|
||||
run func(runnerId string, ctx context.Context, command []string, tty bool, stdin io.Reader, stdout, stderr io.Writer) (int, error)) {
|
||||
call := api.On("ExecuteCommand",
|
||||
mock.AnythingOfType("string"),
|
||||
mock.Anything,
|
||||
request.FullCommand(),
|
||||
mock.AnythingOfType("bool"),
|
||||
mock.Anything,
|
||||
mock.Anything,
|
||||
mock.Anything)
|
||||
@ -126,9 +127,25 @@ func MockApiExecute(api *nomad.ExecutorApiMock, request *dto.ExecutionRequest,
|
||||
exit, err := run(args.Get(0).(string),
|
||||
args.Get(1).(context.Context),
|
||||
args.Get(2).([]string),
|
||||
args.Get(3).(io.Reader),
|
||||
args.Get(4).(io.Writer),
|
||||
args.Get(5).(io.Writer))
|
||||
args.Get(3).(bool),
|
||||
args.Get(4).(io.Reader),
|
||||
args.Get(5).(io.Writer),
|
||||
args.Get(6).(io.Writer))
|
||||
call.ReturnArguments = mock.Arguments{exit, err}
|
||||
})
|
||||
}
|
||||
|
||||
// HttpDelete sends a Delete Http Request with body to the passed url.
|
||||
func HttpDelete(url string, body io.Reader) (response *http.Response, err error) {
|
||||
req, _ := http.NewRequest(http.MethodDelete, url, body)
|
||||
client := &http.Client{}
|
||||
return client.Do(req)
|
||||
}
|
||||
|
||||
// HttpPatch sends a Patch Http Request with body to the passed url.
|
||||
func HttpPatch(url string, contentType string, body io.Reader) (response *http.Response, err error) {
|
||||
req, _ := http.NewRequest(http.MethodPatch, url, body)
|
||||
req.Header.Set("Content-Type", contentType)
|
||||
client := &http.Client{}
|
||||
return client.Do(req)
|
||||
}
|
Reference in New Issue
Block a user