Implement routes to list, get and delete execution environments

* #9 Implement routes to list, get and delete execution environments.
A refactoring was required to introduce the ExecutionEnvironment interface.

* Fix MR comments, linting issues and bug that lead to e2e test failure

* Add e2e tests

* Add unit tests
This commit is contained in:
Maximilian Paß
2021-10-21 10:33:52 +02:00
committed by GitHub
parent 71cf21abce
commit 34d4bb7ea0
31 changed files with 2239 additions and 1065 deletions

View File

@ -20,10 +20,8 @@ const (
AnotherEnvironmentIDAsString = "42"
DefaultUUID = "MY-DEFAULT-RANDOM-UUID"
AnotherUUID = "another-uuid-43"
DefaultJobID = DefaultEnvironmentIDAsString + "-" + DefaultUUID
AnotherJobID = AnotherEnvironmentIDAsString + "-" + AnotherUUID
DefaultRunnerID = DefaultJobID
AnotherRunnerID = AnotherJobID
DefaultRunnerID = DefaultEnvironmentIDAsString + "-" + DefaultUUID
AnotherRunnerID = AnotherEnvironmentIDAsString + "-" + AnotherUUID
DefaultExecutionID = "s0m3-3x3cu710n-1d"
DefaultMockID = "m0ck-1d"
ShortTimeout = 100 * time.Millisecond

View File

@ -65,7 +65,7 @@ func TestMain(m *testing.M) {
<-time.After(10 * time.Second)
code := m.Run()
cleanupJobsForEnvironment(&testing.T{}, "0")
cleanupJobsForEnvironment(&testing.T{}, tests.DefaultEnvironmentIDAsString)
os.Exit(code)
}

View File

@ -1,9 +1,10 @@
package e2e
import (
"encoding/json"
nomadApi "github.com/hashicorp/nomad/api"
"github.com/openHPI/poseidon/internal/api"
"github.com/openHPI/poseidon/internal/runner"
"github.com/openHPI/poseidon/internal/nomad"
"github.com/openHPI/poseidon/pkg/dto"
"github.com/openHPI/poseidon/tests"
"github.com/openHPI/poseidon/tests/helpers"
@ -65,7 +66,213 @@ func TestCreateOrUpdateEnvironment(t *testing.T) {
validateJob(t, request)
})
cleanupJobsForEnvironment(t, tests.AnotherEnvironmentIDAsString)
deleteEnvironment(t, tests.AnotherEnvironmentIDAsString)
}
func TestListEnvironments(t *testing.T) {
path := helpers.BuildURL(api.BasePath, api.EnvironmentsPath)
t.Run("returns list with one element", func(t *testing.T) {
response, err := http.Get(path) //nolint:gosec // because we build this path right above
require.NoError(t, err)
assert.Equal(t, http.StatusOK, response.StatusCode)
environmentsArray := assertEnvironmentArrayInResponse(t, response)
assert.Equal(t, 1, len(environmentsArray))
})
t.Run("returns list including the default environment", func(t *testing.T) {
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, 1, 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)
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) {
// Add environment without Poseidon
_, job := helpers.CreateTemplateJob()
jobID := nomad.TemplateJobID(tests.AnotherEnvironmentIDAsInteger)
job.ID = &jobID
job.Name = &jobID
_, _, err := nomadClient.Jobs().Register(job, nil)
require.NoError(t, err)
// List without fetch should not include the added environment
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, 1, len(environmentsArray))
assertEnvironment(t, environmentsArray[0], tests.DefaultEnvironmentIDAsInteger)
// List with fetch should include the added environment
response, err = http.Get(path + "?fetch=true") //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)
assert.Contains(t, foundIDs, dto.EnvironmentID(tests.AnotherEnvironmentIDAsInteger))
})
deleteEnvironment(t, tests.AnotherEnvironmentIDAsString)
}
func TestGetEnvironment(t *testing.T) {
t.Run("returns the default environment", func(t *testing.T) {
path := helpers.BuildURL(api.BasePath, api.EnvironmentsPath, tests.DefaultEnvironmentIDAsString)
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)
environment := getEnvironmentFromResponse(t, response)
assertEnvironment(t, environment, tests.DefaultEnvironmentIDAsInteger)
})
t.Run("Added environments can be retrieved without fetch", func(t *testing.T) {
createEnvironment(t, 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
require.NoError(t, err)
require.Equal(t, http.StatusOK, response.StatusCode)
environment := getEnvironmentFromResponse(t, response)
assertEnvironment(t, environment, tests.AnotherEnvironmentIDAsInteger)
})
deleteEnvironment(t, tests.AnotherEnvironmentIDAsString)
t.Run("Added environments can be retrieved with fetch", func(t *testing.T) {
// Add environment without Poseidon
_, job := helpers.CreateTemplateJob()
jobID := nomad.TemplateJobID(tests.AnotherEnvironmentIDAsInteger)
job.ID = &jobID
job.Name = &jobID
_, _, err := nomadClient.Jobs().Register(job, nil)
require.NoError(t, err)
// List without fetch should not include the added environment
path := helpers.BuildURL(api.BasePath, api.EnvironmentsPath, tests.AnotherEnvironmentIDAsString)
response, err := http.Get(path) //nolint:gosec // because we build this path right above
require.NoError(t, err)
require.Equal(t, http.StatusNotFound, response.StatusCode)
// List with fetch should include the added environment
response, err = http.Get(path + "?fetch=true") //nolint:gosec // because we build this path right above
require.NoError(t, err)
require.Equal(t, http.StatusOK, response.StatusCode)
environment := getEnvironmentFromResponse(t, response)
assertEnvironment(t, environment, tests.AnotherEnvironmentIDAsInteger)
})
deleteEnvironment(t, tests.AnotherEnvironmentIDAsString)
}
func TestDeleteEnvironment(t *testing.T) {
t.Run("Removes added environment", func(t *testing.T) {
createEnvironment(t, tests.AnotherEnvironmentIDAsString)
path := helpers.BuildURL(api.BasePath, api.EnvironmentsPath, tests.AnotherEnvironmentIDAsString)
response, err := helpers.HTTPDelete(path, nil)
assert.NoError(t, err)
assert.Equal(t, http.StatusNoContent, response.StatusCode)
})
t.Run("Removes Nomad Job", func(t *testing.T) {
createEnvironment(t, tests.AnotherEnvironmentIDAsString)
// Expect created Nomad job
jobID := nomad.TemplateJobID(tests.AnotherEnvironmentIDAsInteger)
job, _, err := nomadClient.Jobs().Info(jobID, nil)
assert.NoError(t, err)
assert.Equal(t, jobID, *job.ID)
// Delete the job
path := helpers.BuildURL(api.BasePath, api.EnvironmentsPath, tests.AnotherEnvironmentIDAsString)
response, err := helpers.HTTPDelete(path, nil)
assert.NoError(t, err)
assert.Equal(t, http.StatusNoContent, response.StatusCode)
// Expect not to find the Nomad job
_, _, err = nomadClient.Jobs().Info(jobID, nil)
assert.Error(t, err)
})
}
func parseIDsFromEnvironments(t *testing.T, environments []interface{}) (ids []dto.EnvironmentID) {
t.Helper()
for _, environment := range environments {
id, _ := parseEnvironment(t, environment)
ids = append(ids, id)
}
return ids
}
func assertEnvironment(t *testing.T, environment interface{}, expectedID dto.EnvironmentID) {
t.Helper()
id, defaultEnvironmentParams := parseEnvironment(t, environment)
assert.Equal(t, expectedID, id)
expectedKeys := []string{"prewarmingPoolSize", "cpuLimit", "memoryLimit", "image", "networkAccess", "exposedPorts"}
for _, key := range expectedKeys {
_, ok := defaultEnvironmentParams[key]
assert.True(t, ok)
}
}
func parseEnvironment(t *testing.T, environment interface{}) (id dto.EnvironmentID, params map[string]interface{}) {
t.Helper()
environmentParams, ok := environment.(map[string]interface{})
require.True(t, ok)
idInterface, ok := environmentParams["id"]
require.True(t, ok)
idFloat, ok := idInterface.(float64)
require.True(t, ok)
return dto.EnvironmentID(int(idFloat)), environmentParams
}
func assertEnvironmentArrayInResponse(t *testing.T, response *http.Response) []interface{} {
t.Helper()
paramMap := make(map[string]interface{})
err := json.NewDecoder(response.Body).Decode(&paramMap)
require.NoError(t, err)
environments, ok := paramMap["executionEnvironments"]
assert.True(t, ok)
environmentsArray, ok := environments.([]interface{})
assert.True(t, ok)
return environmentsArray
}
func getEnvironmentFromResponse(t *testing.T, response *http.Response) interface{} {
t.Helper()
var environment interface{}
err := json.NewDecoder(response.Body).Decode(&environment)
require.NoError(t, err)
return environment
}
//nolint:unparam // Because its more clear if the environment id is written in the real test
func deleteEnvironment(t *testing.T, id string) {
t.Helper()
path := helpers.BuildURL(api.BasePath, api.EnvironmentsPath, id)
_, err := helpers.HTTPDelete(path, nil)
require.NoError(t, err)
}
func cleanupJobsForEnvironment(t *testing.T, environmentID string) {
@ -84,6 +291,21 @@ func cleanupJobsForEnvironment(t *testing.T, environmentID string) {
}
}
//nolint:unparam // Because its more clear if the environment id is written in the real test
func createEnvironment(t *testing.T, environmentID string) {
t.Helper()
path := helpers.BuildURL(api.BasePath, api.EnvironmentsPath, environmentID)
request := dto.ExecutionEnvironmentRequest{
PrewarmingPoolSize: 1,
CPULimit: 100,
MemoryLimit: 100,
Image: *testDockerImage,
NetworkAccess: false,
ExposedPorts: nil,
}
assertPutReturnsStatusAndZeroContent(t, path, request, http.StatusCreated)
}
func assertPutReturnsStatusAndZeroContent(t *testing.T, path string,
request dto.ExecutionEnvironmentRequest, status int) {
t.Helper()
@ -133,9 +355,9 @@ func validateJob(t *testing.T, expected dto.ExecutionEnvironmentRequest) {
}
}
func findTemplateJob(t *testing.T, id runner.EnvironmentID) *nomadApi.Job {
func findTemplateJob(t *testing.T, id dto.EnvironmentID) *nomadApi.Job {
t.Helper()
job, _, err := nomadClient.Jobs().Info(runner.TemplateJobID(id), nil)
job, _, err := nomadClient.Jobs().Info(nomad.TemplateJobID(id), nil)
if err != nil {
t.Fatalf("Error retrieving Nomad job: %v", err)
}

View File

@ -174,8 +174,9 @@ func HTTPPutJSON(url string, body interface{}) (response *http.Response, err err
const templateJobPriority = 100
func CreateTemplateJob() (base, job *nomadApi.Job) {
base = nomadApi.NewBatchJob(tests.DefaultJobID, tests.DefaultJobID, "region-name", templateJobPriority)
job = nomadApi.NewBatchJob(tests.DefaultJobID, tests.DefaultJobID, "region-name", templateJobPriority)
base = nomadApi.NewBatchJob(tests.DefaultRunnerID, tests.DefaultRunnerID, "global", templateJobPriority)
job = nomadApi.NewBatchJob(tests.DefaultRunnerID, tests.DefaultRunnerID, "global", templateJobPriority)
job.Datacenters = []string{"dc1"}
configTaskGroup := nomadApi.NewTaskGroup("config", 0)
configTaskGroup.Meta = make(map[string]string)
configTaskGroup.Meta["prewarmingPoolSize"] = "0"