Parametrize e2e tests to also check AWS environments.
- Fix destroy runner after timeout. - Add file deletion
This commit is contained in:
4
.github/workflows/ci.yml
vendored
4
.github/workflows/ci.yml
vendored
@ -106,6 +106,10 @@ jobs:
|
|||||||
e2e-test:
|
e2e-test:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: [ compile, dep-scan, test ]
|
needs: [ compile, dep-scan, test ]
|
||||||
|
env:
|
||||||
|
POSEIDON_AWS_ENABLED: true
|
||||||
|
POSEIDON_AWS_ENDPOINT: ${{ secrets.POSEIDON_AWS_ENDPOINT }}
|
||||||
|
POSEIDON_AWS_FUNCTIONS: ${{ secrets.POSEIDON_AWS_FUNCTIONS }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
|
@ -8,12 +8,13 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type AWSEnvironment struct {
|
type AWSEnvironment struct {
|
||||||
id dto.EnvironmentID
|
id dto.EnvironmentID
|
||||||
awsEndpoint string
|
awsEndpoint string
|
||||||
|
onDestroyRunner runner.DestroyRunnerHandler
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewAWSEnvironment() *AWSEnvironment {
|
func NewAWSEnvironment(onDestroyRunner runner.DestroyRunnerHandler) *AWSEnvironment {
|
||||||
return &AWSEnvironment{}
|
return &AWSEnvironment{onDestroyRunner: onDestroyRunner}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *AWSEnvironment) MarshalJSON() ([]byte, error) {
|
func (a *AWSEnvironment) MarshalJSON() ([]byte, error) {
|
||||||
@ -86,11 +87,11 @@ func (a *AWSEnvironment) Register() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *AWSEnvironment) Delete() error {
|
func (a *AWSEnvironment) Delete() error {
|
||||||
panic("implement me")
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *AWSEnvironment) Sample() (r runner.Runner, ok bool) {
|
func (a *AWSEnvironment) Sample() (r runner.Runner, ok bool) {
|
||||||
workload, err := runner.NewAWSFunctionWorkload(a, nil)
|
workload, err := runner.NewAWSFunctionWorkload(a, a.onDestroyRunner)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, false
|
return nil, false
|
||||||
}
|
}
|
||||||
|
@ -53,7 +53,7 @@ func (a *AWSEnvironmentManager) CreateOrUpdate(
|
|||||||
}
|
}
|
||||||
|
|
||||||
_, ok := a.runnerManager.GetEnvironment(id)
|
_, ok := a.runnerManager.GetEnvironment(id)
|
||||||
e := NewAWSEnvironment()
|
e := NewAWSEnvironment(a.runnerManager.Return)
|
||||||
e.SetID(id)
|
e.SetID(id)
|
||||||
e.SetImage(request.Image)
|
e.SetImage(request.Image)
|
||||||
a.runnerManager.StoreEnvironment(e)
|
a.runnerManager.StoreEnvironment(e)
|
||||||
|
@ -66,7 +66,7 @@ func TestAWSEnvironmentManager_Get(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
t.Run("Returns environment when it was added before", func(t *testing.T) {
|
t.Run("Returns environment when it was added before", func(t *testing.T) {
|
||||||
expectedEnvironment := NewAWSEnvironment()
|
expectedEnvironment := NewAWSEnvironment(nil)
|
||||||
expectedEnvironment.SetID(tests.DefaultEnvironmentIDAsInteger)
|
expectedEnvironment.SetID(tests.DefaultEnvironmentIDAsInteger)
|
||||||
runnerManager.StoreEnvironment(expectedEnvironment)
|
runnerManager.StoreEnvironment(expectedEnvironment)
|
||||||
|
|
||||||
@ -82,7 +82,7 @@ func TestAWSEnvironmentManager_List(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("returs also environments of the rest of the manager chain", func(t *testing.T) {
|
t.Run("returs also environments of the rest of the manager chain", func(t *testing.T) {
|
||||||
nextHandler := &ManagerHandlerMock{}
|
nextHandler := &ManagerHandlerMock{}
|
||||||
existingEnvironment := NewAWSEnvironment()
|
existingEnvironment := NewAWSEnvironment(nil)
|
||||||
nextHandler.On("List", mock.AnythingOfType("bool")).
|
nextHandler.On("List", mock.AnythingOfType("bool")).
|
||||||
Return([]runner.ExecutionEnvironment{existingEnvironment}, nil)
|
Return([]runner.ExecutionEnvironment{existingEnvironment}, nil)
|
||||||
m.SetNextHandler(nextHandler)
|
m.SetNextHandler(nextHandler)
|
||||||
@ -95,7 +95,7 @@ func TestAWSEnvironmentManager_List(t *testing.T) {
|
|||||||
m.SetNextHandler(nil)
|
m.SetNextHandler(nil)
|
||||||
|
|
||||||
t.Run("Returns added environment", func(t *testing.T) {
|
t.Run("Returns added environment", func(t *testing.T) {
|
||||||
localEnvironment := NewAWSEnvironment()
|
localEnvironment := NewAWSEnvironment(nil)
|
||||||
localEnvironment.SetID(tests.DefaultEnvironmentIDAsInteger)
|
localEnvironment.SetID(tests.DefaultEnvironmentIDAsInteger)
|
||||||
runnerManager.StoreEnvironment(localEnvironment)
|
runnerManager.StoreEnvironment(localEnvironment)
|
||||||
|
|
||||||
|
@ -24,29 +24,33 @@ type awsFunctionRequest struct {
|
|||||||
// AWSFunctionWorkload is an abstraction to build a request to an AWS Lambda Function.
|
// AWSFunctionWorkload is an abstraction to build a request to an AWS Lambda Function.
|
||||||
type AWSFunctionWorkload struct {
|
type AWSFunctionWorkload struct {
|
||||||
InactivityTimer
|
InactivityTimer
|
||||||
id string
|
id string
|
||||||
fs map[dto.FilePath][]byte
|
fs map[dto.FilePath][]byte
|
||||||
executions execution.Storer
|
executions execution.Storer
|
||||||
onDestroy destroyRunnerHandler
|
runningExecutions map[execution.ID]context.CancelFunc
|
||||||
environment ExecutionEnvironment
|
onDestroy DestroyRunnerHandler
|
||||||
|
environment ExecutionEnvironment
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewAWSFunctionWorkload creates a new AWSFunctionWorkload with the provided id.
|
// NewAWSFunctionWorkload creates a new AWSFunctionWorkload with the provided id.
|
||||||
func NewAWSFunctionWorkload(
|
func NewAWSFunctionWorkload(
|
||||||
environment ExecutionEnvironment, onDestroy destroyRunnerHandler) (*AWSFunctionWorkload, error) {
|
environment ExecutionEnvironment, onDestroy DestroyRunnerHandler) (*AWSFunctionWorkload, error) {
|
||||||
newUUID, err := uuid.NewUUID()
|
newUUID, err := uuid.NewUUID()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed generating runner id: %w", err)
|
return nil, fmt.Errorf("failed generating runner id: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
workload := &AWSFunctionWorkload{
|
workload := &AWSFunctionWorkload{
|
||||||
id: newUUID.String(),
|
id: newUUID.String(),
|
||||||
fs: make(map[dto.FilePath][]byte),
|
fs: make(map[dto.FilePath][]byte),
|
||||||
executions: execution.NewLocalStorage(),
|
executions: execution.NewLocalStorage(),
|
||||||
onDestroy: onDestroy,
|
runningExecutions: make(map[execution.ID]context.CancelFunc),
|
||||||
environment: environment,
|
onDestroy: onDestroy,
|
||||||
|
environment: environment,
|
||||||
}
|
}
|
||||||
workload.InactivityTimer = NewInactivityTimer(workload, onDestroy)
|
workload.InactivityTimer = NewInactivityTimer(workload, func(_ Runner) error {
|
||||||
|
return workload.Destroy()
|
||||||
|
})
|
||||||
return workload, nil
|
return workload, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -73,16 +77,21 @@ func (w *AWSFunctionWorkload) ExecuteInteractively(id string, _ io.ReadWriter, s
|
|||||||
if !ok {
|
if !ok {
|
||||||
return nil, nil, ErrorUnknownExecution
|
return nil, nil, ErrorUnknownExecution
|
||||||
}
|
}
|
||||||
|
|
||||||
command, ctx, cancel := prepareExecution(request)
|
command, ctx, cancel := prepareExecution(request)
|
||||||
|
exitInternal := make(chan ExitInfo)
|
||||||
exit := make(chan ExitInfo, 1)
|
exit := make(chan ExitInfo, 1)
|
||||||
go w.executeCommand(ctx, command, stdout, stderr, exit)
|
|
||||||
|
go w.executeCommand(ctx, command, stdout, stderr, exitInternal)
|
||||||
|
go w.handleRunnerTimeout(ctx, exitInternal, exit, execution.ID(id))
|
||||||
|
|
||||||
return exit, cancel, nil
|
return exit, cancel, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateFileSystem copies Files into the executor.
|
// UpdateFileSystem copies Files into the executor.
|
||||||
// ToDo: Currently, file deletion is not supported (but it could be).
|
|
||||||
func (w *AWSFunctionWorkload) UpdateFileSystem(request *dto.UpdateFileSystemRequest) error {
|
func (w *AWSFunctionWorkload) UpdateFileSystem(request *dto.UpdateFileSystemRequest) error {
|
||||||
|
for _, path := range request.Delete {
|
||||||
|
delete(w.fs, path)
|
||||||
|
}
|
||||||
for _, file := range request.Copy {
|
for _, file := range request.Copy {
|
||||||
w.fs[file.Path] = file.Content
|
w.fs[file.Path] = file.Content
|
||||||
}
|
}
|
||||||
@ -90,6 +99,9 @@ func (w *AWSFunctionWorkload) UpdateFileSystem(request *dto.UpdateFileSystemRequ
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (w *AWSFunctionWorkload) Destroy() error {
|
func (w *AWSFunctionWorkload) Destroy() error {
|
||||||
|
for _, cancel := range w.runningExecutions {
|
||||||
|
cancel()
|
||||||
|
}
|
||||||
if err := w.onDestroy(w); err != nil {
|
if err := w.onDestroy(w); err != nil {
|
||||||
return fmt.Errorf("error while destroying aws runner: %w", err)
|
return fmt.Errorf("error while destroying aws runner: %w", err)
|
||||||
}
|
}
|
||||||
@ -99,6 +111,7 @@ func (w *AWSFunctionWorkload) Destroy() error {
|
|||||||
func (w *AWSFunctionWorkload) executeCommand(ctx context.Context, command []string,
|
func (w *AWSFunctionWorkload) executeCommand(ctx context.Context, command []string,
|
||||||
stdout, stderr io.Writer, exit chan<- ExitInfo,
|
stdout, stderr io.Writer, exit chan<- ExitInfo,
|
||||||
) {
|
) {
|
||||||
|
defer close(exit)
|
||||||
data := &awsFunctionRequest{
|
data := &awsFunctionRequest{
|
||||||
Action: w.environment.Image(),
|
Action: w.environment.Image(),
|
||||||
Cmd: command,
|
Cmd: command,
|
||||||
@ -128,7 +141,6 @@ func (w *AWSFunctionWorkload) executeCommand(ctx context.Context, command []stri
|
|||||||
err = ErrorRunnerInactivityTimeout
|
err = ErrorRunnerInactivityTimeout
|
||||||
}
|
}
|
||||||
exit <- ExitInfo{exitCode, err}
|
exit <- ExitInfo{exitCode, err}
|
||||||
close(exit)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *AWSFunctionWorkload) receiveOutput(
|
func (w *AWSFunctionWorkload) receiveOutput(
|
||||||
@ -157,7 +169,7 @@ func (w *AWSFunctionWorkload) receiveOutput(
|
|||||||
case dto.WebSocketOutputStdout:
|
case dto.WebSocketOutputStdout:
|
||||||
// We do not check the written bytes as the rawToCodeOceanWriter receives everything or nothing.
|
// We do not check the written bytes as the rawToCodeOceanWriter receives everything or nothing.
|
||||||
_, err = stdout.Write([]byte(wsMessage.Data))
|
_, err = stdout.Write([]byte(wsMessage.Data))
|
||||||
case dto.WebSocketOutputStderr:
|
case dto.WebSocketOutputStderr, dto.WebSocketOutputError:
|
||||||
_, err = stderr.Write([]byte(wsMessage.Data))
|
_, err = stderr.Write([]byte(wsMessage.Data))
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -166,3 +178,20 @@ func (w *AWSFunctionWorkload) receiveOutput(
|
|||||||
}
|
}
|
||||||
return 1, fmt.Errorf("receiveOutput stpped by context: %w", ctx.Err())
|
return 1, fmt.Errorf("receiveOutput stpped by context: %w", ctx.Err())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// handleRunnerTimeout listens for a runner timeout and aborts the execution in that case.
|
||||||
|
// It listens via a context in runningExecutions that is canceled on the timeout event.
|
||||||
|
func (w *AWSFunctionWorkload) handleRunnerTimeout(ctx context.Context,
|
||||||
|
exitInternal <-chan ExitInfo, exit chan<- ExitInfo, id execution.ID) {
|
||||||
|
executionCtx, cancelExecution := context.WithCancel(ctx)
|
||||||
|
w.runningExecutions[id] = cancelExecution
|
||||||
|
defer delete(w.runningExecutions, id)
|
||||||
|
defer close(exit)
|
||||||
|
|
||||||
|
select {
|
||||||
|
case exitInfo := <-exitInternal:
|
||||||
|
exit <- exitInfo
|
||||||
|
case <-executionCtx.Done():
|
||||||
|
exit <- ExitInfo{255, ErrorRunnerInactivityTimeout}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -37,11 +37,11 @@ type InactivityTimerImplementation struct {
|
|||||||
duration time.Duration
|
duration time.Duration
|
||||||
state TimerState
|
state TimerState
|
||||||
runner Runner
|
runner Runner
|
||||||
onDestroy destroyRunnerHandler
|
onDestroy DestroyRunnerHandler
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewInactivityTimer(runner Runner, onDestroy destroyRunnerHandler) InactivityTimer {
|
func NewInactivityTimer(runner Runner, onDestroy DestroyRunnerHandler) InactivityTimer {
|
||||||
return &InactivityTimerImplementation{
|
return &InactivityTimerImplementation{
|
||||||
state: TimerInactive,
|
state: TimerInactive,
|
||||||
runner: runner,
|
runner: runner,
|
||||||
|
@ -41,12 +41,12 @@ type NomadJob struct {
|
|||||||
id string
|
id string
|
||||||
portMappings []nomadApi.PortMapping
|
portMappings []nomadApi.PortMapping
|
||||||
api nomad.ExecutorAPI
|
api nomad.ExecutorAPI
|
||||||
onDestroy destroyRunnerHandler
|
onDestroy DestroyRunnerHandler
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewNomadJob creates a new NomadJob with the provided id.
|
// NewNomadJob creates a new NomadJob with the provided id.
|
||||||
func NewNomadJob(id string, portMappings []nomadApi.PortMapping,
|
func NewNomadJob(id string, portMappings []nomadApi.PortMapping,
|
||||||
apiClient nomad.ExecutorAPI, onDestroy destroyRunnerHandler,
|
apiClient nomad.ExecutorAPI, onDestroy DestroyRunnerHandler,
|
||||||
) *NomadJob {
|
) *NomadJob {
|
||||||
job := &NomadJob{
|
job := &NomadJob{
|
||||||
id: id,
|
id: id,
|
||||||
|
@ -391,7 +391,7 @@ func (s *UpdateFileSystemTestSuite) readFilesFromTarArchive(tarArchive io.Reader
|
|||||||
|
|
||||||
// NewRunner creates a new runner with the provided id and manager.
|
// NewRunner creates a new runner with the provided id and manager.
|
||||||
func NewRunner(id string, manager Accessor) Runner {
|
func NewRunner(id string, manager Accessor) Runner {
|
||||||
var handler destroyRunnerHandler
|
var handler DestroyRunnerHandler
|
||||||
if manager != nil {
|
if manager != nil {
|
||||||
handler = manager.Return
|
handler = manager.Return
|
||||||
} else {
|
} else {
|
||||||
|
@ -11,7 +11,7 @@ type ExitInfo struct {
|
|||||||
Err error
|
Err error
|
||||||
}
|
}
|
||||||
|
|
||||||
type destroyRunnerHandler = func(r Runner) error
|
type DestroyRunnerHandler = func(r Runner) error
|
||||||
|
|
||||||
type Runner interface {
|
type Runner interface {
|
||||||
InactivityTimer
|
InactivityTimer
|
||||||
|
@ -12,6 +12,7 @@ import (
|
|||||||
"github.com/stretchr/testify/suite"
|
"github.com/stretchr/testify/suite"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
@ -27,6 +28,7 @@ var (
|
|||||||
testDockerImage = flag.String("dockerImage", "", "Docker image to use in E2E tests")
|
testDockerImage = flag.String("dockerImage", "", "Docker image to use in E2E tests")
|
||||||
nomadClient *nomadApi.Client
|
nomadClient *nomadApi.Client
|
||||||
nomadNamespace string
|
nomadNamespace string
|
||||||
|
environmentIDs []dto.EnvironmentID
|
||||||
)
|
)
|
||||||
|
|
||||||
type E2ETestSuite struct {
|
type E2ETestSuite struct {
|
||||||
@ -45,11 +47,42 @@ func TestE2ETestSuite(t *testing.T) {
|
|||||||
// Overwrite TestMain for custom setup.
|
// Overwrite TestMain for custom setup.
|
||||||
func TestMain(m *testing.M) {
|
func TestMain(m *testing.M) {
|
||||||
log.Info("Test Setup")
|
log.Info("Test Setup")
|
||||||
err := config.InitConfig()
|
if err := config.InitConfig(); err != nil {
|
||||||
if err != nil {
|
|
||||||
log.WithError(err).Fatal("Could not initialize configuration")
|
log.WithError(err).Fatal("Could not initialize configuration")
|
||||||
}
|
}
|
||||||
|
initNomad()
|
||||||
|
initAWS()
|
||||||
|
|
||||||
|
// wait for environment to become ready
|
||||||
|
<-time.After(10 * time.Second)
|
||||||
|
log.Info("Test Run")
|
||||||
|
code := m.Run()
|
||||||
|
|
||||||
|
deleteE2EEnvironments()
|
||||||
|
cleanupJobsForEnvironment(&testing.T{}, tests.DefaultEnvironmentIDAsString)
|
||||||
|
os.Exit(code)
|
||||||
|
}
|
||||||
|
|
||||||
|
func initAWS() {
|
||||||
|
for i, function := range strings.Fields(config.Config.AWS.Functions) {
|
||||||
|
id := dto.EnvironmentID(tests.DefaultEnvironmentIDAsInteger + i + 1)
|
||||||
|
path := helpers.BuildURL(api.BasePath, api.EnvironmentsPath, id.ToString())
|
||||||
|
request := dto.ExecutionEnvironmentRequest{Image: function}
|
||||||
|
resp, err := helpers.HTTPPutJSON(path, request)
|
||||||
|
if err != nil || resp.StatusCode != http.StatusCreated && resp.StatusCode != http.StatusNoContent {
|
||||||
|
log.WithField("function", function).WithError(err).Fatal("Couldn't create default environment for e2e tests")
|
||||||
|
}
|
||||||
|
environmentIDs = append(environmentIDs, id)
|
||||||
|
err = resp.Body.Close()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("Failed closing body")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func initNomad() {
|
||||||
nomadNamespace = config.Config.Nomad.Namespace
|
nomadNamespace = config.Config.Nomad.Namespace
|
||||||
|
var err error
|
||||||
nomadClient, err = nomadApi.NewClient(&nomadApi.Config{
|
nomadClient, err = nomadApi.NewClient(&nomadApi.Config{
|
||||||
Address: config.Config.Nomad.URL().String(),
|
Address: config.Config.Nomad.URL().String(),
|
||||||
TLSConfig: &nomadApi.TLSConfig{},
|
TLSConfig: &nomadApi.TLSConfig{},
|
||||||
@ -57,16 +90,9 @@ func TestMain(m *testing.M) {
|
|||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.WithError(err).Fatal("Could not create Nomad client")
|
log.WithError(err).Fatal("Could not create Nomad client")
|
||||||
|
return
|
||||||
}
|
}
|
||||||
log.Info("Test Run")
|
|
||||||
createDefaultEnvironment()
|
createDefaultEnvironment()
|
||||||
|
|
||||||
// wait for environment to become ready
|
|
||||||
<-time.After(10 * time.Second)
|
|
||||||
|
|
||||||
code := m.Run()
|
|
||||||
cleanupJobsForEnvironment(&testing.T{}, tests.DefaultEnvironmentIDAsString)
|
|
||||||
os.Exit(code)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func createDefaultEnvironment() {
|
func createDefaultEnvironment() {
|
||||||
@ -89,8 +115,15 @@ func createDefaultEnvironment() {
|
|||||||
if err != nil || resp.StatusCode != http.StatusCreated && resp.StatusCode != http.StatusNoContent {
|
if err != nil || resp.StatusCode != http.StatusCreated && resp.StatusCode != http.StatusNoContent {
|
||||||
log.WithError(err).Fatal("Couldn't create default environment for e2e tests")
|
log.WithError(err).Fatal("Couldn't create default environment for e2e tests")
|
||||||
}
|
}
|
||||||
|
environmentIDs = append(environmentIDs, tests.DefaultEnvironmentIDAsInteger)
|
||||||
err = resp.Body.Close()
|
err = resp.Body.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal("Failed closing body")
|
log.Fatal("Failed closing body")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func deleteE2EEnvironments() {
|
||||||
|
for _, id := range environmentIDs {
|
||||||
|
deleteEnvironment(&testing.T{}, id.ToString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -2,8 +2,10 @@ package e2e
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
nomadApi "github.com/hashicorp/nomad/api"
|
nomadApi "github.com/hashicorp/nomad/api"
|
||||||
"github.com/openHPI/poseidon/internal/api"
|
"github.com/openHPI/poseidon/internal/api"
|
||||||
|
"github.com/openHPI/poseidon/internal/config"
|
||||||
"github.com/openHPI/poseidon/internal/nomad"
|
"github.com/openHPI/poseidon/internal/nomad"
|
||||||
"github.com/openHPI/poseidon/pkg/dto"
|
"github.com/openHPI/poseidon/pkg/dto"
|
||||||
"github.com/openHPI/poseidon/tests"
|
"github.com/openHPI/poseidon/tests"
|
||||||
@ -17,6 +19,8 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var isAWSEnvironment = []bool{false, true}
|
||||||
|
|
||||||
func TestCreateOrUpdateEnvironment(t *testing.T) {
|
func TestCreateOrUpdateEnvironment(t *testing.T) {
|
||||||
path := helpers.BuildURL(api.BasePath, api.EnvironmentsPath, tests.AnotherEnvironmentIDAsString)
|
path := helpers.BuildURL(api.BasePath, api.EnvironmentsPath, tests.AnotherEnvironmentIDAsString)
|
||||||
|
|
||||||
@ -73,13 +77,13 @@ func TestCreateOrUpdateEnvironment(t *testing.T) {
|
|||||||
func TestListEnvironments(t *testing.T) {
|
func TestListEnvironments(t *testing.T) {
|
||||||
path := helpers.BuildURL(api.BasePath, api.EnvironmentsPath)
|
path := helpers.BuildURL(api.BasePath, api.EnvironmentsPath)
|
||||||
|
|
||||||
t.Run("returns list with one element", func(t *testing.T) {
|
t.Run("returns list with all static and the e2e environment", func(t *testing.T) {
|
||||||
response, err := http.Get(path) //nolint:gosec // because we build this path right above
|
response, err := http.Get(path) //nolint:gosec // because we build this path right above
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
assert.Equal(t, http.StatusOK, response.StatusCode)
|
assert.Equal(t, http.StatusOK, response.StatusCode)
|
||||||
environmentsArray := assertEnvironmentArrayInResponse(t, response)
|
environmentsArray := assertEnvironmentArrayInResponse(t, response)
|
||||||
assert.Equal(t, 1, len(environmentsArray))
|
assert.Equal(t, len(environmentIDs), len(environmentsArray))
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("returns list including the default environment", func(t *testing.T) {
|
t.Run("returns list including the default environment", func(t *testing.T) {
|
||||||
@ -88,24 +92,28 @@ func TestListEnvironments(t *testing.T) {
|
|||||||
require.Equal(t, http.StatusOK, response.StatusCode)
|
require.Equal(t, http.StatusOK, response.StatusCode)
|
||||||
|
|
||||||
environmentsArray := assertEnvironmentArrayInResponse(t, response)
|
environmentsArray := assertEnvironmentArrayInResponse(t, response)
|
||||||
require.Equal(t, 1, len(environmentsArray))
|
require.Equal(t, len(environmentIDs), len(environmentsArray))
|
||||||
|
|
||||||
assertEnvironment(t, environmentsArray[0], tests.DefaultEnvironmentIDAsInteger)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("Added environments can be retrieved without fetch", func(t *testing.T) {
|
|
||||||
createEnvironment(t, tests.AnotherEnvironmentIDAsString)
|
|
||||||
|
|
||||||
response, err := http.Get(path) //nolint:gosec // because we build this path right above
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, http.StatusOK, response.StatusCode)
|
|
||||||
|
|
||||||
environmentsArray := assertEnvironmentArrayInResponse(t, response)
|
|
||||||
require.Equal(t, 2, len(environmentsArray))
|
|
||||||
foundIDs := parseIDsFromEnvironments(t, environmentsArray)
|
foundIDs := parseIDsFromEnvironments(t, environmentsArray)
|
||||||
assert.Contains(t, foundIDs, dto.EnvironmentID(tests.AnotherEnvironmentIDAsInteger))
|
assert.Contains(t, foundIDs, dto.EnvironmentID(tests.DefaultEnvironmentIDAsInteger))
|
||||||
})
|
})
|
||||||
deleteEnvironment(t, tests.AnotherEnvironmentIDAsString)
|
|
||||||
|
for _, useAWS := range isAWSEnvironment {
|
||||||
|
t.Run(fmt.Sprintf("AWS-%t", useAWS), func(t *testing.T) {
|
||||||
|
t.Run("Added environments can be retrieved without fetch", func(t *testing.T) {
|
||||||
|
createEnvironment(t, tests.AnotherEnvironmentIDAsString, useAWS)
|
||||||
|
|
||||||
|
response, err := http.Get(path) //nolint:gosec // because we build this path right above
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, http.StatusOK, response.StatusCode)
|
||||||
|
|
||||||
|
environmentsArray := assertEnvironmentArrayInResponse(t, response)
|
||||||
|
require.Equal(t, len(environmentIDs)+1, len(environmentsArray))
|
||||||
|
foundIDs := parseIDsFromEnvironments(t, environmentsArray)
|
||||||
|
assert.Contains(t, foundIDs, dto.EnvironmentID(tests.AnotherEnvironmentIDAsInteger))
|
||||||
|
})
|
||||||
|
deleteEnvironment(t, tests.AnotherEnvironmentIDAsString)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
t.Run("Added environments can be retrieved with fetch", func(t *testing.T) {
|
t.Run("Added environments can be retrieved with fetch", func(t *testing.T) {
|
||||||
// Add environment without Poseidon
|
// Add environment without Poseidon
|
||||||
@ -122,16 +130,17 @@ func TestListEnvironments(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, http.StatusOK, response.StatusCode)
|
require.Equal(t, http.StatusOK, response.StatusCode)
|
||||||
environmentsArray := assertEnvironmentArrayInResponse(t, response)
|
environmentsArray := assertEnvironmentArrayInResponse(t, response)
|
||||||
require.Equal(t, 1, len(environmentsArray))
|
require.Equal(t, len(environmentIDs), len(environmentsArray))
|
||||||
assertEnvironment(t, environmentsArray[0], tests.DefaultEnvironmentIDAsInteger)
|
foundIDs := parseIDsFromEnvironments(t, environmentsArray)
|
||||||
|
assert.Contains(t, foundIDs, dto.EnvironmentID(tests.DefaultEnvironmentIDAsInteger))
|
||||||
|
|
||||||
// List with fetch should include the added environment
|
// List with fetch should include the added environment
|
||||||
response, err = http.Get(path + "?fetch=true") //nolint:gosec // because we build this path right above
|
response, err = http.Get(path + "?fetch=true") //nolint:gosec // because we build this path right above
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, http.StatusOK, response.StatusCode)
|
require.Equal(t, http.StatusOK, response.StatusCode)
|
||||||
environmentsArray = assertEnvironmentArrayInResponse(t, response)
|
environmentsArray = assertEnvironmentArrayInResponse(t, response)
|
||||||
require.Equal(t, 2, len(environmentsArray))
|
require.Equal(t, len(environmentIDs)+1, len(environmentsArray))
|
||||||
foundIDs := parseIDsFromEnvironments(t, environmentsArray)
|
foundIDs = parseIDsFromEnvironments(t, environmentsArray)
|
||||||
assert.Contains(t, foundIDs, dto.EnvironmentID(tests.AnotherEnvironmentIDAsInteger))
|
assert.Contains(t, foundIDs, dto.EnvironmentID(tests.AnotherEnvironmentIDAsInteger))
|
||||||
})
|
})
|
||||||
deleteEnvironment(t, tests.AnotherEnvironmentIDAsString)
|
deleteEnvironment(t, tests.AnotherEnvironmentIDAsString)
|
||||||
@ -148,18 +157,22 @@ func TestGetEnvironment(t *testing.T) {
|
|||||||
assertEnvironment(t, environment, tests.DefaultEnvironmentIDAsInteger)
|
assertEnvironment(t, environment, tests.DefaultEnvironmentIDAsInteger)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("Added environments can be retrieved without fetch", func(t *testing.T) {
|
for _, useAWS := range isAWSEnvironment {
|
||||||
createEnvironment(t, tests.AnotherEnvironmentIDAsString)
|
t.Run(fmt.Sprintf("AWS-%t", useAWS), func(t *testing.T) {
|
||||||
|
t.Run("Added environments can be retrieved without fetch", func(t *testing.T) {
|
||||||
|
createEnvironment(t, tests.AnotherEnvironmentIDAsString, useAWS)
|
||||||
|
|
||||||
path := helpers.BuildURL(api.BasePath, api.EnvironmentsPath, tests.AnotherEnvironmentIDAsString)
|
path := helpers.BuildURL(api.BasePath, api.EnvironmentsPath, tests.AnotherEnvironmentIDAsString)
|
||||||
response, err := http.Get(path) //nolint:gosec // because we build this path right above
|
response, err := http.Get(path) //nolint:gosec // because we build this path right above
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, http.StatusOK, response.StatusCode)
|
require.Equal(t, http.StatusOK, response.StatusCode)
|
||||||
|
|
||||||
environment := getEnvironmentFromResponse(t, response)
|
environment := getEnvironmentFromResponse(t, response)
|
||||||
assertEnvironment(t, environment, tests.AnotherEnvironmentIDAsInteger)
|
assertEnvironment(t, environment, tests.AnotherEnvironmentIDAsInteger)
|
||||||
})
|
})
|
||||||
deleteEnvironment(t, tests.AnotherEnvironmentIDAsString)
|
deleteEnvironment(t, tests.AnotherEnvironmentIDAsString)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
t.Run("Added environments can be retrieved with fetch", func(t *testing.T) {
|
t.Run("Added environments can be retrieved with fetch", func(t *testing.T) {
|
||||||
// Add environment without Poseidon
|
// Add environment without Poseidon
|
||||||
@ -188,17 +201,21 @@ func TestGetEnvironment(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestDeleteEnvironment(t *testing.T) {
|
func TestDeleteEnvironment(t *testing.T) {
|
||||||
t.Run("Removes added environment", func(t *testing.T) {
|
for _, useAWS := range isAWSEnvironment {
|
||||||
createEnvironment(t, tests.AnotherEnvironmentIDAsString)
|
t.Run(fmt.Sprintf("AWS-%t", useAWS), func(t *testing.T) {
|
||||||
|
t.Run("Removes added environment", func(t *testing.T) {
|
||||||
|
createEnvironment(t, tests.AnotherEnvironmentIDAsString, useAWS)
|
||||||
|
|
||||||
path := helpers.BuildURL(api.BasePath, api.EnvironmentsPath, tests.AnotherEnvironmentIDAsString)
|
path := helpers.BuildURL(api.BasePath, api.EnvironmentsPath, tests.AnotherEnvironmentIDAsString)
|
||||||
response, err := helpers.HTTPDelete(path, nil)
|
response, err := helpers.HTTPDelete(path, nil)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, http.StatusNoContent, response.StatusCode)
|
assert.Equal(t, http.StatusNoContent, response.StatusCode)
|
||||||
})
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
t.Run("Removes Nomad Job", func(t *testing.T) {
|
t.Run("Removes Nomad Job", func(t *testing.T) {
|
||||||
createEnvironment(t, tests.AnotherEnvironmentIDAsString)
|
createEnvironment(t, tests.AnotherEnvironmentIDAsString, false)
|
||||||
|
|
||||||
// Expect created Nomad job
|
// Expect created Nomad job
|
||||||
jobID := nomad.TemplateJobID(tests.AnotherEnvironmentIDAsInteger)
|
jobID := nomad.TemplateJobID(tests.AnotherEnvironmentIDAsInteger)
|
||||||
@ -295,17 +312,23 @@ func cleanupJobsForEnvironment(t *testing.T, environmentID string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//nolint:unparam // Because its more clear if the environment id is written in the real test
|
//nolint:unparam // Because its more clear if the environment id is written in the real test
|
||||||
func createEnvironment(t *testing.T, environmentID string) {
|
func createEnvironment(t *testing.T, environmentID string, aws bool) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
path := helpers.BuildURL(api.BasePath, api.EnvironmentsPath, environmentID)
|
path := helpers.BuildURL(api.BasePath, api.EnvironmentsPath, environmentID)
|
||||||
request := dto.ExecutionEnvironmentRequest{
|
request := dto.ExecutionEnvironmentRequest{
|
||||||
PrewarmingPoolSize: 1,
|
PrewarmingPoolSize: 1,
|
||||||
CPULimit: 100,
|
CPULimit: 100,
|
||||||
MemoryLimit: 100,
|
MemoryLimit: 100,
|
||||||
Image: *testDockerImage,
|
|
||||||
NetworkAccess: false,
|
NetworkAccess: false,
|
||||||
ExposedPorts: nil,
|
ExposedPorts: nil,
|
||||||
}
|
}
|
||||||
|
if aws {
|
||||||
|
functions := strings.Fields(config.Config.AWS.Functions)
|
||||||
|
require.NotZero(t, len(functions))
|
||||||
|
request.Image = functions[0]
|
||||||
|
} else {
|
||||||
|
request.Image = *testDockerImage
|
||||||
|
}
|
||||||
assertPutReturnsStatusAndZeroContent(t, path, request, http.StatusCreated)
|
assertPutReturnsStatusAndZeroContent(t, path, request, http.StatusCreated)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,39 +16,41 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func (s *E2ETestSuite) TestProvideRunnerRoute() {
|
func (s *E2ETestSuite) TestProvideRunnerRoute() {
|
||||||
runnerRequestByteString, err := json.Marshal(dto.RunnerRequest{
|
for _, environmentID := range environmentIDs {
|
||||||
ExecutionEnvironmentID: tests.DefaultEnvironmentIDAsInteger,
|
s.Run(environmentID.ToString(), func() {
|
||||||
})
|
runnerRequestByteString, err := json.Marshal(dto.RunnerRequest{ExecutionEnvironmentID: int(environmentID)})
|
||||||
s.Require().NoError(err)
|
s.Require().NoError(err)
|
||||||
reader := bytes.NewReader(runnerRequestByteString)
|
reader := bytes.NewReader(runnerRequestByteString)
|
||||||
|
|
||||||
s.Run("valid request returns a runner", func() {
|
s.Run("valid request returns a runner", func() {
|
||||||
resp, err := http.Post(helpers.BuildURL(api.BasePath, api.RunnersPath), "application/json", reader)
|
resp, err := http.Post(helpers.BuildURL(api.BasePath, api.RunnersPath), "application/json", reader)
|
||||||
s.Require().NoError(err)
|
s.Require().NoError(err)
|
||||||
s.Equal(http.StatusOK, resp.StatusCode)
|
s.Equal(http.StatusOK, resp.StatusCode)
|
||||||
|
|
||||||
runnerResponse := new(dto.RunnerResponse)
|
runnerResponse := new(dto.RunnerResponse)
|
||||||
err = json.NewDecoder(resp.Body).Decode(runnerResponse)
|
err = json.NewDecoder(resp.Body).Decode(runnerResponse)
|
||||||
s.Require().NoError(err)
|
s.Require().NoError(err)
|
||||||
s.NotEmpty(runnerResponse.ID)
|
s.NotEmpty(runnerResponse.ID)
|
||||||
})
|
})
|
||||||
|
|
||||||
s.Run("invalid request returns bad request", func() {
|
s.Run("invalid request returns bad request", func() {
|
||||||
resp, err := http.Post(helpers.BuildURL(api.BasePath, api.RunnersPath), "application/json", strings.NewReader(""))
|
resp, err := http.Post(helpers.BuildURL(api.BasePath, api.RunnersPath), "application/json", strings.NewReader(""))
|
||||||
s.Require().NoError(err)
|
s.Require().NoError(err)
|
||||||
s.Equal(http.StatusBadRequest, resp.StatusCode)
|
s.Equal(http.StatusBadRequest, resp.StatusCode)
|
||||||
})
|
})
|
||||||
|
|
||||||
s.Run("requesting runner of unknown execution environment returns not found", func() {
|
s.Run("requesting runner of unknown execution environment returns not found", func() {
|
||||||
runnerRequestByteString, err := json.Marshal(dto.RunnerRequest{
|
runnerRequestByteString, err := json.Marshal(dto.RunnerRequest{
|
||||||
ExecutionEnvironmentID: tests.NonExistingIntegerID,
|
ExecutionEnvironmentID: tests.NonExistingIntegerID,
|
||||||
|
})
|
||||||
|
s.Require().NoError(err)
|
||||||
|
reader := bytes.NewReader(runnerRequestByteString)
|
||||||
|
resp, err := http.Post(helpers.BuildURL(api.BasePath, api.RunnersPath), "application/json", reader)
|
||||||
|
s.Require().NoError(err)
|
||||||
|
s.Equal(http.StatusNotFound, resp.StatusCode)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
s.Require().NoError(err)
|
}
|
||||||
reader := bytes.NewReader(runnerRequestByteString)
|
|
||||||
resp, err := http.Post(helpers.BuildURL(api.BasePath, api.RunnersPath), "application/json", reader)
|
|
||||||
s.Require().NoError(err)
|
|
||||||
s.Equal(http.StatusNotFound, resp.StatusCode)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ProvideRunner creates a runner with the given RunnerRequest via an external request.
|
// ProvideRunner creates a runner with the given RunnerRequest via an external request.
|
||||||
@ -77,117 +79,143 @@ func ProvideRunner(request *dto.RunnerRequest) (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *E2ETestSuite) TestDeleteRunnerRoute() {
|
func (s *E2ETestSuite) TestDeleteRunnerRoute() {
|
||||||
runnerID, err := ProvideRunner(&dto.RunnerRequest{
|
for _, environmentID := range environmentIDs {
|
||||||
ExecutionEnvironmentID: tests.DefaultEnvironmentIDAsInteger,
|
s.Run(environmentID.ToString(), func() {
|
||||||
})
|
runnerID, err := ProvideRunner(&dto.RunnerRequest{ExecutionEnvironmentID: int(environmentID)})
|
||||||
s.NoError(err)
|
s.NoError(err)
|
||||||
|
|
||||||
s.Run("Deleting the runner returns NoContent", func() {
|
s.Run("Deleting the runner returns NoContent", func() {
|
||||||
resp, err := helpers.HTTPDelete(helpers.BuildURL(api.BasePath, api.RunnersPath, runnerID), nil)
|
resp, err := helpers.HTTPDelete(helpers.BuildURL(api.BasePath, api.RunnersPath, runnerID), nil)
|
||||||
s.NoError(err)
|
s.NoError(err)
|
||||||
s.Equal(http.StatusNoContent, resp.StatusCode)
|
s.Equal(http.StatusNoContent, resp.StatusCode)
|
||||||
})
|
})
|
||||||
|
|
||||||
s.Run("Deleting it again returns NotFound", func() {
|
s.Run("Deleting it again returns NotFound", func() {
|
||||||
resp, err := helpers.HTTPDelete(helpers.BuildURL(api.BasePath, api.RunnersPath, runnerID), nil)
|
resp, err := helpers.HTTPDelete(helpers.BuildURL(api.BasePath, api.RunnersPath, runnerID), nil)
|
||||||
s.NoError(err)
|
s.NoError(err)
|
||||||
s.Equal(http.StatusNotFound, resp.StatusCode)
|
s.Equal(http.StatusNotFound, resp.StatusCode)
|
||||||
})
|
})
|
||||||
|
|
||||||
s.Run("Deleting non-existing runner returns NotFound", func() {
|
s.Run("Deleting non-existing runner returns NotFound", func() {
|
||||||
resp, err := helpers.HTTPDelete(helpers.BuildURL(api.BasePath, api.RunnersPath, tests.NonExistingStringID), nil)
|
resp, err := helpers.HTTPDelete(helpers.BuildURL(api.BasePath, api.RunnersPath, tests.NonExistingStringID), nil)
|
||||||
s.NoError(err)
|
s.NoError(err)
|
||||||
s.Equal(http.StatusNotFound, resp.StatusCode)
|
s.Equal(http.StatusNotFound, resp.StatusCode)
|
||||||
})
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//nolint:funlen // there are a lot of tests for the files route, this function can be a little longer than 100 lines ;)
|
//nolint:funlen // there are a lot of tests for the files route, this function can be a little longer than 100 lines ;)
|
||||||
func (s *E2ETestSuite) TestCopyFilesRoute() {
|
func (s *E2ETestSuite) TestCopyFilesRoute() {
|
||||||
runnerID, err := ProvideRunner(&dto.RunnerRequest{
|
for _, environmentID := range environmentIDs {
|
||||||
ExecutionEnvironmentID: tests.DefaultEnvironmentIDAsInteger,
|
s.Run(environmentID.ToString(), func() {
|
||||||
})
|
runnerID, err := ProvideRunner(&dto.RunnerRequest{ExecutionEnvironmentID: int(environmentID)})
|
||||||
s.NoError(err)
|
s.NoError(err)
|
||||||
copyFilesRequestByteString, err := json.Marshal(&dto.UpdateFileSystemRequest{
|
copyFilesRequestByteString, err := json.Marshal(&dto.UpdateFileSystemRequest{
|
||||||
Copy: []dto.File{{Path: tests.DefaultFileName, Content: []byte(tests.DefaultFileContent)}},
|
Copy: []dto.File{{Path: tests.DefaultFileName, Content: []byte(tests.DefaultFileContent)}},
|
||||||
})
|
})
|
||||||
s.Require().NoError(err)
|
s.Require().NoError(err)
|
||||||
sendCopyRequest := func(reader io.Reader) (*http.Response, error) {
|
sendCopyRequest := func(reader io.Reader) (*http.Response, error) {
|
||||||
return helpers.HTTPPatch(helpers.BuildURL(api.BasePath, api.RunnersPath, runnerID, api.UpdateFileSystemPath),
|
return helpers.HTTPPatch(helpers.BuildURL(api.BasePath, api.RunnersPath, runnerID, api.UpdateFileSystemPath),
|
||||||
"application/json", reader)
|
"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.assertFileContent(runnerID, tests.DefaultFileName, tests.DefaultFileContent)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
s.Run("Files are put in correct location", func() {
|
||||||
|
relativeFilePath := "relative/file/path.txt"
|
||||||
|
relativeFileContent := "Relative file content"
|
||||||
|
absoluteFilePath := "/tmp/absolute/file/path.txt"
|
||||||
|
absoluteFileContent := "Absolute file content"
|
||||||
|
testFilePathsCopyRequestString, err := json.Marshal(&dto.UpdateFileSystemRequest{
|
||||||
|
Copy: []dto.File{
|
||||||
|
{Path: dto.FilePath(relativeFilePath), Content: []byte(relativeFileContent)},
|
||||||
|
{Path: dto.FilePath(absoluteFilePath), Content: []byte(absoluteFileContent)},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
s.Require().NoError(err)
|
||||||
|
|
||||||
|
resp, err := sendCopyRequest(bytes.NewReader(testFilePathsCopyRequestString))
|
||||||
|
s.NoError(err)
|
||||||
|
s.Equal(http.StatusNoContent, resp.StatusCode)
|
||||||
|
|
||||||
|
s.Run("File content of file with relative path can be printed on runner", func() {
|
||||||
|
// the print command is executed in the context of the default working directory of the container
|
||||||
|
s.assertFileContent(runnerID, relativeFilePath, relativeFileContent)
|
||||||
|
})
|
||||||
|
|
||||||
|
s.Run("File content of file with absolute path can be printed on runner", func() {
|
||||||
|
s.assertFileContent(runnerID, absoluteFilePath, absoluteFileContent)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
s.Run("File deletion request deletes file on runner", func() {
|
||||||
|
copyFilesRequestByteString, err := json.Marshal(&dto.UpdateFileSystemRequest{
|
||||||
|
Delete: []dto.FilePath{tests.DefaultFileName},
|
||||||
|
})
|
||||||
|
s.Require().NoError(err)
|
||||||
|
|
||||||
|
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() {
|
||||||
|
stdout, stderr := s.PrintContentOfFileOnRunner(runnerID, tests.DefaultFileName)
|
||||||
|
s.Equal("", stdout)
|
||||||
|
s.Contains(stderr, "No such file or directory")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
s.Run("File copy happens after file deletion", func() {
|
||||||
|
copyFilesRequestByteString, err := json.Marshal(&dto.UpdateFileSystemRequest{
|
||||||
|
Delete: []dto.FilePath{tests.DefaultFileName},
|
||||||
|
Copy: []dto.File{{Path: tests.DefaultFileName, Content: []byte(tests.DefaultFileContent)}},
|
||||||
|
})
|
||||||
|
s.Require().NoError(err)
|
||||||
|
|
||||||
|
resp, err := sendCopyRequest(bytes.NewReader(copyFilesRequestByteString))
|
||||||
|
s.NoError(err)
|
||||||
|
s.Equal(http.StatusNoContent, resp.StatusCode)
|
||||||
|
_ = resp.Body.Close()
|
||||||
|
|
||||||
|
s.Run("File content can be printed on runner", func() {
|
||||||
|
s.assertFileContent(runnerID, tests.DefaultFileName, tests.DefaultFileContent)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
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.NonExistingStringID, api.UpdateFileSystemPath),
|
||||||
|
"application/json", bytes.NewReader(copyFilesRequestByteString))
|
||||||
|
s.NoError(err)
|
||||||
|
s.Equal(http.StatusNotFound, resp.StatusCode)
|
||||||
|
})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
s.Run("File copy with valid payload succeeds", func() {
|
func (s *E2ETestSuite) TestCopyFilesRoute_PermissionDenied() {
|
||||||
resp, err := sendCopyRequest(bytes.NewReader(copyFilesRequestByteString))
|
s.Run("Nomad/If one file produces permission denied error, others are still copied", func() {
|
||||||
|
runnerID, err := ProvideRunner(&dto.RunnerRequest{
|
||||||
|
ExecutionEnvironmentID: tests.DefaultEnvironmentIDAsInteger,
|
||||||
|
})
|
||||||
s.NoError(err)
|
s.NoError(err)
|
||||||
s.Equal(http.StatusNoContent, resp.StatusCode)
|
|
||||||
|
|
||||||
s.Run("File content can be printed on runner", func() {
|
|
||||||
s.assertFileContent(runnerID, tests.DefaultFileName, tests.DefaultFileContent)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
s.Run("Files are put in correct location", func() {
|
|
||||||
relativeFilePath := "relative/file/path.txt"
|
|
||||||
relativeFileContent := "Relative file content"
|
|
||||||
absoluteFilePath := "/tmp/absolute/file/path.txt"
|
|
||||||
absoluteFileContent := "Absolute file content"
|
|
||||||
testFilePathsCopyRequestString, err := json.Marshal(&dto.UpdateFileSystemRequest{
|
|
||||||
Copy: []dto.File{
|
|
||||||
{Path: dto.FilePath(relativeFilePath), Content: []byte(relativeFileContent)},
|
|
||||||
{Path: dto.FilePath(absoluteFilePath), Content: []byte(absoluteFileContent)},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
s.Require().NoError(err)
|
|
||||||
|
|
||||||
resp, err := sendCopyRequest(bytes.NewReader(testFilePathsCopyRequestString))
|
|
||||||
s.NoError(err)
|
|
||||||
s.Equal(http.StatusNoContent, resp.StatusCode)
|
|
||||||
|
|
||||||
s.Run("File content of file with relative path can be printed on runner", func() {
|
|
||||||
// the print command is executed in the context of the default working directory of the container
|
|
||||||
s.assertFileContent(runnerID, relativeFilePath, relativeFileContent)
|
|
||||||
})
|
|
||||||
|
|
||||||
s.Run("File content of file with absolute path can be printed on runner", func() {
|
|
||||||
s.assertFileContent(runnerID, absoluteFilePath, absoluteFileContent)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
s.Run("File deletion request deletes file on runner", func() {
|
|
||||||
copyFilesRequestByteString, err := json.Marshal(&dto.UpdateFileSystemRequest{
|
|
||||||
Delete: []dto.FilePath{tests.DefaultFileName},
|
|
||||||
})
|
|
||||||
s.Require().NoError(err)
|
|
||||||
|
|
||||||
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() {
|
|
||||||
stdout, stderr := s.PrintContentOfFileOnRunner(runnerID, tests.DefaultFileName)
|
|
||||||
s.Equal("", stdout)
|
|
||||||
s.Contains(stderr, "No such file or directory")
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
s.Run("File copy happens after file deletion", func() {
|
|
||||||
copyFilesRequestByteString, err := json.Marshal(&dto.UpdateFileSystemRequest{
|
|
||||||
Delete: []dto.FilePath{tests.DefaultFileName},
|
|
||||||
Copy: []dto.File{{Path: tests.DefaultFileName, Content: []byte(tests.DefaultFileContent)}},
|
|
||||||
})
|
|
||||||
s.Require().NoError(err)
|
|
||||||
|
|
||||||
resp, err := sendCopyRequest(bytes.NewReader(copyFilesRequestByteString))
|
|
||||||
s.NoError(err)
|
|
||||||
s.Equal(http.StatusNoContent, resp.StatusCode)
|
|
||||||
_ = resp.Body.Close()
|
|
||||||
|
|
||||||
s.Run("File content can be printed on runner", func() {
|
|
||||||
s.assertFileContent(runnerID, tests.DefaultFileName, tests.DefaultFileContent)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
s.Run("If one file produces permission denied error, others are still copied", func() {
|
|
||||||
newFileContent := []byte("New content")
|
newFileContent := []byte("New content")
|
||||||
copyFilesRequestByteString, err := json.Marshal(&dto.UpdateFileSystemRequest{
|
copyFilesRequestByteString, err := json.Marshal(&dto.UpdateFileSystemRequest{
|
||||||
Copy: []dto.File{
|
Copy: []dto.File{
|
||||||
@ -197,7 +225,8 @@ func (s *E2ETestSuite) TestCopyFilesRoute() {
|
|||||||
})
|
})
|
||||||
s.Require().NoError(err)
|
s.Require().NoError(err)
|
||||||
|
|
||||||
resp, err := sendCopyRequest(bytes.NewReader(copyFilesRequestByteString))
|
resp, err := helpers.HTTPPatch(helpers.BuildURL(api.BasePath, api.RunnersPath, runnerID, api.UpdateFileSystemPath),
|
||||||
|
"application/json", bytes.NewReader(copyFilesRequestByteString))
|
||||||
s.NoError(err)
|
s.NoError(err)
|
||||||
s.Equal(http.StatusInternalServerError, resp.StatusCode)
|
s.Equal(http.StatusInternalServerError, resp.StatusCode)
|
||||||
internalServerError := new(dto.InternalServerError)
|
internalServerError := new(dto.InternalServerError)
|
||||||
@ -211,49 +240,70 @@ func (s *E2ETestSuite) TestCopyFilesRoute() {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
s.Run("File copy with invalid payload returns bad request", func() {
|
s.Run("AWS/If one file produces permission denied error, others are still copied", func() {
|
||||||
resp, err := helpers.HTTPPatch(helpers.BuildURL(api.BasePath, api.RunnersPath, runnerID, api.UpdateFileSystemPath),
|
for _, environmentID := range environmentIDs {
|
||||||
"text/html", strings.NewReader(""))
|
if environmentID == tests.DefaultEnvironmentIDAsInteger {
|
||||||
s.NoError(err)
|
continue
|
||||||
s.Equal(http.StatusBadRequest, resp.StatusCode)
|
}
|
||||||
})
|
s.Run(environmentID.ToString(), func() {
|
||||||
|
runnerID, err := ProvideRunner(&dto.RunnerRequest{ExecutionEnvironmentID: int(environmentID)})
|
||||||
|
s.NoError(err)
|
||||||
|
|
||||||
s.Run("Copying to non-existing runner returns NotFound", func() {
|
newFileContent := []byte("New content")
|
||||||
resp, err := helpers.HTTPPatch(
|
copyFilesRequestByteString, err := json.Marshal(&dto.UpdateFileSystemRequest{
|
||||||
helpers.BuildURL(api.BasePath, api.RunnersPath, tests.NonExistingStringID, api.UpdateFileSystemPath),
|
Copy: []dto.File{
|
||||||
"application/json", bytes.NewReader(copyFilesRequestByteString))
|
{Path: "/dev/sda", Content: []byte(tests.DefaultFileContent)},
|
||||||
s.NoError(err)
|
{Path: tests.DefaultFileName, Content: newFileContent},
|
||||||
s.Equal(http.StatusNotFound, resp.StatusCode)
|
},
|
||||||
|
})
|
||||||
|
s.Require().NoError(err)
|
||||||
|
|
||||||
|
resp, err := helpers.HTTPPatch(helpers.BuildURL(api.BasePath, api.RunnersPath, runnerID, api.UpdateFileSystemPath),
|
||||||
|
"application/json", bytes.NewReader(copyFilesRequestByteString))
|
||||||
|
s.NoError(err)
|
||||||
|
s.Equal(http.StatusNoContent, resp.StatusCode)
|
||||||
|
_ = resp.Body.Close()
|
||||||
|
|
||||||
|
stdout, stderr := s.PrintContentOfFileOnRunner(runnerID, tests.DefaultFileName)
|
||||||
|
s.Equal(string(newFileContent), stdout)
|
||||||
|
s.Contains(stderr, "Permission denied")
|
||||||
|
})
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *E2ETestSuite) TestRunnerGetsDestroyedAfterInactivityTimeout() {
|
func (s *E2ETestSuite) TestRunnerGetsDestroyedAfterInactivityTimeout() {
|
||||||
inactivityTimeout := 5 // seconds
|
for _, environmentID := range environmentIDs {
|
||||||
runnerID, err := ProvideRunner(&dto.RunnerRequest{
|
s.Run(environmentID.ToString(), func() {
|
||||||
ExecutionEnvironmentID: tests.DefaultEnvironmentIDAsInteger,
|
inactivityTimeout := 2 // seconds
|
||||||
InactivityTimeout: inactivityTimeout,
|
runnerID, err := ProvideRunner(&dto.RunnerRequest{
|
||||||
})
|
ExecutionEnvironmentID: int(environmentID),
|
||||||
s.Require().NoError(err)
|
InactivityTimeout: inactivityTimeout,
|
||||||
|
})
|
||||||
|
s.Require().NoError(err)
|
||||||
|
|
||||||
executionTerminated := make(chan bool)
|
executionTerminated := make(chan bool)
|
||||||
var lastMessage *dto.WebSocketMessage
|
var lastMessage *dto.WebSocketMessage
|
||||||
go func() {
|
go func() {
|
||||||
webSocketURL, err := ProvideWebSocketURL(&s.Suite, runnerID, &dto.ExecutionRequest{Command: "sleep infinity"})
|
webSocketURL, err := ProvideWebSocketURL(&s.Suite, runnerID, &dto.ExecutionRequest{Command: "sleep infinity"})
|
||||||
s.Require().NoError(err)
|
s.Require().NoError(err)
|
||||||
connection, err := ConnectToWebSocket(webSocketURL)
|
connection, err := ConnectToWebSocket(webSocketURL)
|
||||||
s.Require().NoError(err)
|
s.Require().NoError(err)
|
||||||
|
|
||||||
messages, err := helpers.ReceiveAllWebSocketMessages(connection)
|
messages, err := helpers.ReceiveAllWebSocketMessages(connection)
|
||||||
if !s.Equal(&websocket.CloseError{Code: websocket.CloseNormalClosure}, err) {
|
if !s.Equal(&websocket.CloseError{Code: websocket.CloseNormalClosure}, err) {
|
||||||
s.Fail("websocket abnormal closure")
|
s.Fail("websocket abnormal closure")
|
||||||
}
|
}
|
||||||
controlMessages := helpers.WebSocketControlMessages(messages)
|
controlMessages := helpers.WebSocketControlMessages(messages)
|
||||||
s.Require().NotEmpty(controlMessages)
|
s.Require().NotEmpty(controlMessages)
|
||||||
lastMessage = controlMessages[len(controlMessages)-1]
|
lastMessage = controlMessages[len(controlMessages)-1]
|
||||||
executionTerminated <- true
|
log.Warn("")
|
||||||
}()
|
executionTerminated <- true
|
||||||
s.Require().True(tests.ChannelReceivesSomething(executionTerminated, time.Duration(inactivityTimeout+5)*time.Second))
|
}()
|
||||||
s.Equal(dto.WebSocketMetaTimeout, lastMessage.Type)
|
s.Require().True(tests.ChannelReceivesSomething(executionTerminated, time.Duration(inactivityTimeout+5)*time.Second))
|
||||||
|
s.Equal(dto.WebSocketMetaTimeout, lastMessage.Type)
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *E2ETestSuite) assertFileContent(runnerID, fileName, expectedContent string) {
|
func (s *E2ETestSuite) assertFileContent(runnerID, fileName, expectedContent string) {
|
||||||
@ -263,8 +313,10 @@ func (s *E2ETestSuite) assertFileContent(runnerID, fileName, expectedContent str
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *E2ETestSuite) PrintContentOfFileOnRunner(runnerID, filename string) (stdout, stderr string) {
|
func (s *E2ETestSuite) PrintContentOfFileOnRunner(runnerID, filename string) (stdout, stderr string) {
|
||||||
webSocketURL, err := ProvideWebSocketURL(&s.Suite, runnerID,
|
webSocketURL, err := ProvideWebSocketURL(&s.Suite, runnerID, &dto.ExecutionRequest{
|
||||||
&dto.ExecutionRequest{Command: fmt.Sprintf("cat %s", filename)})
|
Command: fmt.Sprintf("cat %s", filename),
|
||||||
|
TimeLimit: int(tests.DefaultTestTimeout.Seconds()),
|
||||||
|
})
|
||||||
s.Require().NoError(err)
|
s.Require().NoError(err)
|
||||||
connection, err := ConnectToWebSocket(webSocketURL)
|
connection, err := ConnectToWebSocket(webSocketURL)
|
||||||
s.Require().NoError(err)
|
s.Require().NoError(err)
|
||||||
|
@ -18,81 +18,95 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func (s *E2ETestSuite) TestExecuteCommandRoute() {
|
func (s *E2ETestSuite) TestExecuteCommandRoute() {
|
||||||
runnerID, err := ProvideRunner(&dto.RunnerRequest{
|
for _, environmentID := range environmentIDs {
|
||||||
ExecutionEnvironmentID: tests.DefaultEnvironmentIDAsInteger,
|
s.Run(environmentID.ToString(), func() {
|
||||||
})
|
runnerID, err := ProvideRunner(&dto.RunnerRequest{ExecutionEnvironmentID: int(environmentID)})
|
||||||
s.Require().NoError(err)
|
s.Require().NoError(err)
|
||||||
|
|
||||||
webSocketURL, err := ProvideWebSocketURL(&s.Suite, runnerID, &dto.ExecutionRequest{Command: "true"})
|
webSocketURL, err := ProvideWebSocketURL(&s.Suite, runnerID, &dto.ExecutionRequest{Command: "true"})
|
||||||
s.Require().NoError(err)
|
s.Require().NoError(err)
|
||||||
s.NotEqual("", webSocketURL)
|
s.NotEqual("", webSocketURL)
|
||||||
|
|
||||||
var connection *websocket.Conn
|
var connection *websocket.Conn
|
||||||
var connectionClosed bool
|
var connectionClosed bool
|
||||||
|
|
||||||
connection, err = ConnectToWebSocket(webSocketURL)
|
connection, err = ConnectToWebSocket(webSocketURL)
|
||||||
s.Require().NoError(err, "websocket connects")
|
s.Require().NoError(err, "websocket connects")
|
||||||
closeHandler := connection.CloseHandler()
|
closeHandler := connection.CloseHandler()
|
||||||
connection.SetCloseHandler(func(code int, text string) error {
|
connection.SetCloseHandler(func(code int, text string) error {
|
||||||
connectionClosed = true
|
connectionClosed = true
|
||||||
return closeHandler(code, text)
|
return closeHandler(code, text)
|
||||||
})
|
})
|
||||||
|
|
||||||
startMessage, err := helpers.ReceiveNextWebSocketMessage(connection)
|
startMessage, err := helpers.ReceiveNextWebSocketMessage(connection)
|
||||||
s.Require().NoError(err)
|
s.Require().NoError(err)
|
||||||
s.Equal(&dto.WebSocketMessage{Type: dto.WebSocketMetaStart}, startMessage)
|
s.Equal(&dto.WebSocketMessage{Type: dto.WebSocketMetaStart}, startMessage)
|
||||||
|
|
||||||
exitMessage, err := helpers.ReceiveNextWebSocketMessage(connection)
|
exitMessage, err := helpers.ReceiveNextWebSocketMessage(connection)
|
||||||
s.Require().NoError(err)
|
s.Require().NoError(err)
|
||||||
s.Equal(&dto.WebSocketMessage{Type: dto.WebSocketExit}, exitMessage)
|
s.Equal(&dto.WebSocketMessage{Type: dto.WebSocketExit}, exitMessage)
|
||||||
|
|
||||||
_, err = helpers.ReceiveAllWebSocketMessages(connection)
|
_, err = helpers.ReceiveAllWebSocketMessages(connection)
|
||||||
s.Require().Error(err)
|
s.Require().Error(err)
|
||||||
s.Equal(&websocket.CloseError{Code: websocket.CloseNormalClosure}, err)
|
s.Equal(&websocket.CloseError{Code: websocket.CloseNormalClosure}, err)
|
||||||
|
|
||||||
_, _, err = connection.ReadMessage()
|
_, _, err = connection.ReadMessage()
|
||||||
s.True(websocket.IsCloseError(err, websocket.CloseNormalClosure))
|
s.True(websocket.IsCloseError(err, websocket.CloseNormalClosure))
|
||||||
s.True(connectionClosed, "connection should be closed")
|
s.True(connectionClosed, "connection should be closed")
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *E2ETestSuite) TestOutputToStdout() {
|
func (s *E2ETestSuite) TestOutputToStdout() {
|
||||||
connection, err := ProvideWebSocketConnection(&s.Suite, &dto.ExecutionRequest{Command: "echo Hello World"})
|
for _, environmentID := range environmentIDs {
|
||||||
s.Require().NoError(err)
|
s.Run(environmentID.ToString(), func() {
|
||||||
|
connection, err := ProvideWebSocketConnection(&s.Suite, environmentID,
|
||||||
|
&dto.ExecutionRequest{Command: "echo -n Hello World"})
|
||||||
|
s.Require().NoError(err)
|
||||||
|
|
||||||
messages, err := helpers.ReceiveAllWebSocketMessages(connection)
|
messages, err := helpers.ReceiveAllWebSocketMessages(connection)
|
||||||
s.Require().Error(err)
|
s.Require().Error(err)
|
||||||
s.Equal(&websocket.CloseError{Code: websocket.CloseNormalClosure}, err)
|
s.Equal(&websocket.CloseError{Code: websocket.CloseNormalClosure}, err)
|
||||||
|
|
||||||
controlMessages := helpers.WebSocketControlMessages(messages)
|
controlMessages := helpers.WebSocketControlMessages(messages)
|
||||||
s.Require().Equal(&dto.WebSocketMessage{Type: dto.WebSocketMetaStart}, controlMessages[0])
|
s.Require().Equal(&dto.WebSocketMessage{Type: dto.WebSocketMetaStart}, controlMessages[0])
|
||||||
s.Require().Equal(&dto.WebSocketMessage{Type: dto.WebSocketExit}, controlMessages[1])
|
s.Require().Equal(&dto.WebSocketMessage{Type: dto.WebSocketExit}, controlMessages[1])
|
||||||
|
|
||||||
stdout, _, _ := helpers.WebSocketOutputMessages(messages)
|
stdout, _, _ := helpers.WebSocketOutputMessages(messages)
|
||||||
s.Require().Equal("Hello World\r\n", stdout)
|
s.Require().Equal("Hello World", stdout)
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *E2ETestSuite) TestOutputToStderr() {
|
func (s *E2ETestSuite) TestOutputToStderr() {
|
||||||
connection, err := ProvideWebSocketConnection(&s.Suite, &dto.ExecutionRequest{Command: "cat -invalid"})
|
for _, environmentID := range environmentIDs {
|
||||||
s.Require().NoError(err)
|
s.Run(environmentID.ToString(), func() {
|
||||||
|
connection, err := ProvideWebSocketConnection(&s.Suite, environmentID,
|
||||||
|
&dto.ExecutionRequest{Command: "cat -invalid"})
|
||||||
|
s.Require().NoError(err)
|
||||||
|
|
||||||
messages, err := helpers.ReceiveAllWebSocketMessages(connection)
|
messages, err := helpers.ReceiveAllWebSocketMessages(connection)
|
||||||
s.Require().Error(err)
|
s.Require().Error(err)
|
||||||
s.Equal(&websocket.CloseError{Code: websocket.CloseNormalClosure}, err)
|
s.Equal(&websocket.CloseError{Code: websocket.CloseNormalClosure}, err)
|
||||||
|
|
||||||
controlMessages := helpers.WebSocketControlMessages(messages)
|
controlMessages := helpers.WebSocketControlMessages(messages)
|
||||||
s.Require().Equal(2, len(controlMessages))
|
s.Require().Equal(2, len(controlMessages))
|
||||||
s.Require().Equal(&dto.WebSocketMessage{Type: dto.WebSocketMetaStart}, controlMessages[0])
|
s.Require().Equal(&dto.WebSocketMessage{Type: dto.WebSocketMetaStart}, controlMessages[0])
|
||||||
s.Require().Equal(&dto.WebSocketMessage{Type: dto.WebSocketExit, ExitCode: 1}, controlMessages[1])
|
s.Require().Equal(&dto.WebSocketMessage{Type: dto.WebSocketExit, ExitCode: 1}, controlMessages[1])
|
||||||
|
|
||||||
stdout, stderr, errors := helpers.WebSocketOutputMessages(messages)
|
stdout, stderr, errors := helpers.WebSocketOutputMessages(messages)
|
||||||
s.NotContains(stdout, "cat: invalid option", "Stdout should not contain the error")
|
s.NotContains(stdout, "cat: invalid option", "Stdout should not contain the error")
|
||||||
s.Contains(stderr, "cat: invalid option", "Stderr should contain the error")
|
s.Contains(stderr, "cat: invalid option", "Stderr should contain the error")
|
||||||
s.Empty(errors)
|
s.Empty(errors)
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AWS environments do not support stdin at this moment therefore they cannot take this test.
|
||||||
func (s *E2ETestSuite) TestCommandHead() {
|
func (s *E2ETestSuite) TestCommandHead() {
|
||||||
hello := "Hello World!"
|
hello := "Hello World!"
|
||||||
connection, err := ProvideWebSocketConnection(&s.Suite, &dto.ExecutionRequest{Command: "head -n 1"})
|
connection, err := ProvideWebSocketConnection(&s.Suite, tests.DefaultEnvironmentIDAsInteger,
|
||||||
|
&dto.ExecutionRequest{Command: "head -n 1"})
|
||||||
s.Require().NoError(err)
|
s.Require().NoError(err)
|
||||||
|
|
||||||
startMessage, err := helpers.ReceiveNextWebSocketMessage(connection)
|
startMessage, err := helpers.ReceiveNextWebSocketMessage(connection)
|
||||||
@ -110,49 +124,58 @@ func (s *E2ETestSuite) TestCommandHead() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *E2ETestSuite) TestCommandReturnsAfterTimeout() {
|
func (s *E2ETestSuite) TestCommandReturnsAfterTimeout() {
|
||||||
connection, err := ProvideWebSocketConnection(&s.Suite, &dto.ExecutionRequest{Command: "sleep 4", TimeLimit: 1})
|
for _, environmentID := range environmentIDs {
|
||||||
s.Require().NoError(err)
|
s.Run(environmentID.ToString(), func() {
|
||||||
|
connection, err := ProvideWebSocketConnection(&s.Suite, environmentID,
|
||||||
|
&dto.ExecutionRequest{Command: "sleep 4", TimeLimit: 1})
|
||||||
|
s.Require().NoError(err)
|
||||||
|
|
||||||
c := make(chan bool)
|
c := make(chan bool)
|
||||||
var messages []*dto.WebSocketMessage
|
var messages []*dto.WebSocketMessage
|
||||||
go func() {
|
go func() {
|
||||||
messages, err = helpers.ReceiveAllWebSocketMessages(connection)
|
messages, err = helpers.ReceiveAllWebSocketMessages(connection)
|
||||||
if !s.Equal(&websocket.CloseError{Code: websocket.CloseNormalClosure}, err) {
|
if !s.Equal(&websocket.CloseError{Code: websocket.CloseNormalClosure}, err) {
|
||||||
|
s.T().Fail()
|
||||||
|
}
|
||||||
|
close(c)
|
||||||
|
}()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-time.After(2 * time.Second):
|
||||||
|
s.T().Fatal("The execution should have returned by now")
|
||||||
|
case <-c:
|
||||||
|
if s.Equal(&dto.WebSocketMessage{Type: dto.WebSocketMetaTimeout}, messages[len(messages)-1]) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
s.T().Fail()
|
s.T().Fail()
|
||||||
}
|
})
|
||||||
close(c)
|
|
||||||
}()
|
|
||||||
|
|
||||||
select {
|
|
||||||
case <-time.After(2 * time.Second):
|
|
||||||
s.T().Fatal("The execution should have returned by now")
|
|
||||||
case <-c:
|
|
||||||
if s.Equal(&dto.WebSocketMessage{Type: dto.WebSocketMetaTimeout}, messages[len(messages)-1]) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
s.T().Fail()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *E2ETestSuite) TestEchoEnvironment() {
|
func (s *E2ETestSuite) TestEchoEnvironment() {
|
||||||
connection, err := ProvideWebSocketConnection(&s.Suite, &dto.ExecutionRequest{
|
for _, environmentID := range environmentIDs {
|
||||||
Command: "echo $hello",
|
s.Run(environmentID.ToString(), func() {
|
||||||
Environment: map[string]string{"hello": "world"},
|
connection, err := ProvideWebSocketConnection(&s.Suite, environmentID, &dto.ExecutionRequest{
|
||||||
})
|
Command: "echo -n $hello",
|
||||||
s.Require().NoError(err)
|
Environment: map[string]string{"hello": "world"},
|
||||||
|
})
|
||||||
|
s.Require().NoError(err)
|
||||||
|
|
||||||
startMessage, err := helpers.ReceiveNextWebSocketMessage(connection)
|
startMessage, err := helpers.ReceiveNextWebSocketMessage(connection)
|
||||||
s.Require().NoError(err)
|
s.Require().NoError(err)
|
||||||
s.Equal(dto.WebSocketMetaStart, startMessage.Type)
|
s.Equal(dto.WebSocketMetaStart, startMessage.Type)
|
||||||
|
|
||||||
messages, err := helpers.ReceiveAllWebSocketMessages(connection)
|
messages, err := helpers.ReceiveAllWebSocketMessages(connection)
|
||||||
s.Require().Error(err)
|
s.Require().Error(err)
|
||||||
s.Equal(err, &websocket.CloseError{Code: websocket.CloseNormalClosure})
|
s.Equal(err, &websocket.CloseError{Code: websocket.CloseNormalClosure})
|
||||||
stdout, _, _ := helpers.WebSocketOutputMessages(messages)
|
stdout, _, _ := helpers.WebSocketOutputMessages(messages)
|
||||||
s.Equal("world\r\n", stdout)
|
s.Equal("world", stdout)
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *E2ETestSuite) TestStderrFifoIsRemoved() {
|
func (s *E2ETestSuite) TestNomadStderrFifoIsRemoved() {
|
||||||
runnerID, err := ProvideRunner(&dto.RunnerRequest{
|
runnerID, err := ProvideRunner(&dto.RunnerRequest{
|
||||||
ExecutionEnvironmentID: tests.DefaultEnvironmentIDAsInteger,
|
ExecutionEnvironmentID: tests.DefaultEnvironmentIDAsInteger,
|
||||||
})
|
})
|
||||||
@ -191,11 +214,9 @@ func (s *E2ETestSuite) ListTempDirectory(runnerID string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ProvideWebSocketConnection establishes a client WebSocket connection to run the passed ExecutionRequest.
|
// ProvideWebSocketConnection establishes a client WebSocket connection to run the passed ExecutionRequest.
|
||||||
// It requires a running Poseidon instance.
|
func ProvideWebSocketConnection(
|
||||||
func ProvideWebSocketConnection(s *suite.Suite, request *dto.ExecutionRequest) (*websocket.Conn, error) {
|
s *suite.Suite, environmentID dto.EnvironmentID, request *dto.ExecutionRequest) (*websocket.Conn, error) {
|
||||||
runnerID, err := ProvideRunner(&dto.RunnerRequest{
|
runnerID, err := ProvideRunner(&dto.RunnerRequest{ExecutionEnvironmentID: int(environmentID)})
|
||||||
ExecutionEnvironmentID: tests.DefaultEnvironmentIDAsInteger,
|
|
||||||
})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error providing runner: %w", err)
|
return nil, fmt.Errorf("error providing runner: %w", err)
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user