#190 Add recovery e2e tests.

This commit is contained in:
Maximilian Paß
2022-11-23 21:57:30 +00:00
parent 0c6c48c3cf
commit 0b7f71f3dc
9 changed files with 323 additions and 103 deletions

View File

@@ -0,0 +1,136 @@
package recovery
import (
"context"
"encoding/json"
"flag"
nomadApi "github.com/hashicorp/nomad/api"
"github.com/openHPI/poseidon/internal/api"
"github.com/openHPI/poseidon/internal/config"
"github.com/openHPI/poseidon/pkg/dto"
"github.com/openHPI/poseidon/pkg/logging"
"github.com/openHPI/poseidon/tests"
"github.com/openHPI/poseidon/tests/e2e"
"github.com/openHPI/poseidon/tests/helpers"
"github.com/stretchr/testify/suite"
"net/http"
"os"
"testing"
"time"
)
/*
* # E2E Recovery Tests
*
* For the e2e tests a nomad cluster must be connected and poseidon must be running.
* These cases test the behavior of Poseidon when restarting / recovering.
*/
var (
log = logging.GetLogger("e2e-recovery")
testDockerImage = flag.String("dockerImage", "", "Docker image to use in E2E tests")
poseidonBinary = flag.String("poseidonPath", "", "The path to the Poseidon binary")
nomadClient *nomadApi.Client
nomadNamespace string
)
// InactivityTimeout of the created runner in seconds.
const (
InactivityTimeout = 1
PrewarmingPoolSize = 2
)
type E2ERecoveryTestSuite struct {
suite.Suite
runnerID string
poseidonCancel context.CancelFunc
}
// Overwrite TestMain for custom setup.
func TestMain(m *testing.M) {
if err := config.InitConfig(); err != nil {
log.WithError(err).Fatal("Could not initialize configuration")
}
if *poseidonBinary == "" {
log.Fatal("You must specify the -path to the Poseidon binary!")
}
if *testDockerImage == "" {
log.Fatal("You must specify the -dockerImage flag!")
}
nomadNamespace = config.Config.Nomad.Namespace
var err error
nomadClient, err = nomadApi.NewClient(&nomadApi.Config{
Address: config.Config.Nomad.URL().String(),
TLSConfig: &nomadApi.TLSConfig{},
Namespace: nomadNamespace,
})
if err != nil {
log.WithError(err).Fatal("Could not create Nomad client")
return
}
os.Exit(m.Run())
}
func TestE2ERecoveryTests(t *testing.T) {
testSuite := new(E2ERecoveryTestSuite)
ctx, cancelPoseidon := context.WithCancel(context.Background())
testSuite.poseidonCancel = cancelPoseidon
startPoseidon(ctx, cancelPoseidon)
waitForPoseidon()
e2e.CreateDefaultEnvironment(PrewarmingPoolSize, *testDockerImage)
e2e.WaitForDefaultEnvironment()
suite.Run(t, testSuite)
TearDown()
testSuite.poseidonCancel()
<-time.After(tests.ShortTimeout)
}
func (s *E2ERecoveryTestSuite) TestInactivityTimer_Valid() {
_, err := e2e.ProvideWebSocketURL(s.runnerID, &dto.ExecutionRequest{Command: "true"})
s.NoError(err)
}
func (s *E2ERecoveryTestSuite) TestInactivityTimer_Expired() {
<-time.After(InactivityTimeout * time.Second)
_, err := e2e.ProvideWebSocketURL(s.runnerID, &dto.ExecutionRequest{Command: "true"})
s.Error(err)
}
// We expect the runner count to be equal to the prewarming pool size plus the one provided runner.
// If the count does not include the provided runner, the evaluation of the runner status may be wrong.
func (s *E2ERecoveryTestSuite) TestRunnerCount() {
jobListStubs, _, err := nomadClient.Jobs().List(&nomadApi.QueryOptions{
Prefix: tests.DefaultEnvironmentIDAsString,
Namespace: nomadNamespace,
})
s.Require().NoError(err)
s.Equal(PrewarmingPoolSize+1, len(jobListStubs))
}
func (s *E2ERecoveryTestSuite) TestEnvironmentStatistics() {
url := helpers.BuildURL(api.BasePath, api.StatisticsPath, api.EnvironmentsPath)
response, err := http.Get(url) //nolint:gosec // The variability of this url is limited by our configurations.
s.Require().NoError(err)
s.Require().Equal(http.StatusOK, response.StatusCode)
statistics := make(map[string]*dto.StatisticalExecutionEnvironmentData)
err = json.NewDecoder(response.Body).Decode(&statistics)
s.Require().NoError(err)
err = response.Body.Close()
s.Require().NoError(err)
environmentStatistics, ok := statistics[tests.DefaultEnvironmentIDAsString]
s.Require().True(ok)
s.Equal(tests.DefaultEnvironmentIDAsInteger, environmentStatistics.ID)
s.Equal(uint(PrewarmingPoolSize), environmentStatistics.PrewarmingPoolSize)
s.Equal(uint(PrewarmingPoolSize), environmentStatistics.IdleRunners)
s.Equal(uint(1), environmentStatistics.UsedRunners)
}

View File

@@ -0,0 +1,63 @@
package recovery
import (
"context"
"github.com/openHPI/poseidon/internal/api"
"github.com/openHPI/poseidon/pkg/dto"
"github.com/openHPI/poseidon/tests"
"github.com/openHPI/poseidon/tests/e2e"
"github.com/openHPI/poseidon/tests/helpers"
"net/http"
"os"
"os/exec"
"time"
)
func (s *E2ERecoveryTestSuite) SetupTest() {
<-time.After(InactivityTimeout * time.Second)
// We do not want runner from the previous tests
var err error
s.runnerID, err = e2e.ProvideRunner(&dto.RunnerRequest{
ExecutionEnvironmentID: tests.DefaultEnvironmentIDAsInteger,
InactivityTimeout: InactivityTimeout,
})
if err != nil {
log.WithError(err).Fatal("Could not provide runner")
}
<-time.After(tests.ShortTimeout)
s.poseidonCancel()
<-time.After(tests.ShortTimeout)
ctx, cancelPoseidon := context.WithCancel(context.Background())
s.poseidonCancel = cancelPoseidon
startPoseidon(ctx, cancelPoseidon)
waitForPoseidon()
}
func TearDown() {
path := helpers.BuildURL(api.BasePath, api.EnvironmentsPath, tests.DefaultEnvironmentIDAsString)
_, err := helpers.HTTPDelete(path, nil)
if err != nil {
log.WithError(err).Fatal("Could not remove default environment")
}
}
func startPoseidon(ctx context.Context, cancelPoseidon context.CancelFunc) {
poseidon := exec.CommandContext(ctx, *poseidonBinary) //nolint:gosec // We accept that another binary can be executed.
poseidon.Stdout = os.Stdout
poseidon.Stderr = os.Stderr
if err := poseidon.Start(); err != nil {
cancelPoseidon()
log.WithError(err).Fatal("Failed to start Poseidon")
}
}
func waitForPoseidon() {
done := false
for !done {
<-time.After(time.Second)
resp, err := http.Get(helpers.BuildURL(api.BasePath, api.HealthPath))
done = err == nil && resp.StatusCode == http.StatusNoContent
}
}