Add tests for runners execute route

Co-authored-by: Tobias Kantusch <tobias.kantusch@student.hpi.uni-potsdam.de>
This commit is contained in:
Konrad Hanff
2021-04-30 16:23:00 +02:00
parent 6a00ea3165
commit 612bc55bdd
10 changed files with 300 additions and 90 deletions

View File

@ -3,7 +3,7 @@ package api
import ( import (
"github.com/gorilla/mux" "github.com/gorilla/mux"
"gitlab.hpi.de/codeocean/codemoon/poseidon/api/auth" "gitlab.hpi.de/codeocean/codemoon/poseidon/api/auth"
"gitlab.hpi.de/codeocean/codemoon/poseidon/api/dto" "gitlab.hpi.de/codeocean/codemoon/poseidon/environment/pool"
"gitlab.hpi.de/codeocean/codemoon/poseidon/logging" "gitlab.hpi.de/codeocean/codemoon/poseidon/logging"
"net/http" "net/http"
) )
@ -21,18 +21,18 @@ const (
// always returns a router for the newest version of our API. We // always returns a router for the newest version of our API. We
// use gorilla/mux because it is more convenient than net/http, e.g. // use gorilla/mux because it is more convenient than net/http, e.g.
// when extracting path parameters. // when extracting path parameters.
func NewRouter() http.Handler { func NewRouter(runnerPool pool.RunnerPool) *mux.Router {
router := mux.NewRouter() router := mux.NewRouter()
router.Use(logging.HTTPLoggingMiddleware)
// this can later be restricted to a specific host with // this can later be restricted to a specific host with
// `router.Host(...)` and to HTTPS with `router.Schemes("https")` // `router.Host(...)` and to HTTPS with `router.Schemes("https")`
newRouterV1(router) router = newRouterV1(router, runnerPool)
router.Use(logging.HTTPLoggingMiddleware)
return router return router
} }
// newRouterV1 returns a sub-router containing the routes of version // newRouterV1 returns a sub-router containing the routes of version
// 1 of our API. // 1 of our API.
func newRouterV1(router *mux.Router) *mux.Router { func newRouterV1(router *mux.Router, runnerPool pool.RunnerPool) *mux.Router {
v1 := router.PathPrefix(RouteBase).Subrouter() v1 := router.PathPrefix(RouteBase).Subrouter()
v1.HandleFunc(RouteHealth, Health).Methods(http.MethodGet) v1.HandleFunc(RouteHealth, Health).Methods(http.MethodGet)
@ -42,7 +42,7 @@ func newRouterV1(router *mux.Router) *mux.Router {
v1 = v1.PathPrefix("").Subrouter() v1 = v1.PathPrefix("").Subrouter()
v1.Use(auth.HTTPAuthenticationMiddleware) v1.Use(auth.HTTPAuthenticationMiddleware)
} }
registerRunnerRoutes(v1.PathPrefix(RouteRunners).Subrouter()) registerRunnerRoutes(v1.PathPrefix(RouteRunners).Subrouter(), runnerPool)
return v1 return v1
} }

View File

@ -4,6 +4,7 @@ import (
"github.com/gorilla/mux" "github.com/gorilla/mux"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"gitlab.hpi.de/codeocean/codemoon/poseidon/config" "gitlab.hpi.de/codeocean/codemoon/poseidon/config"
"gitlab.hpi.de/codeocean/codemoon/poseidon/environment/pool"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"testing" "testing"
@ -16,7 +17,7 @@ func mockHTTPHandler(writer http.ResponseWriter, _ *http.Request) {
func TestNewRouterV1WithAuthenticationDisabled(t *testing.T) { func TestNewRouterV1WithAuthenticationDisabled(t *testing.T) {
config.Config.Server.Token = "" config.Config.Server.Token = ""
router := mux.NewRouter() router := mux.NewRouter()
v1 := newRouterV1(router) v1 := newRouterV1(router, pool.NewLocalRunnerPool())
t.Run("health route is accessible", func(t *testing.T) { t.Run("health route is accessible", func(t *testing.T) {
request, err := http.NewRequest(http.MethodGet, "/api/v1/health", nil) request, err := http.NewRequest(http.MethodGet, "/api/v1/health", nil)
@ -43,7 +44,7 @@ func TestNewRouterV1WithAuthenticationDisabled(t *testing.T) {
func TestNewRouterV1WithAuthenticationEnabled(t *testing.T) { func TestNewRouterV1WithAuthenticationEnabled(t *testing.T) {
config.Config.Server.Token = "TestToken" config.Config.Server.Token = "TestToken"
router := mux.NewRouter() router := mux.NewRouter()
v1 := newRouterV1(router) v1 := newRouterV1(router, pool.NewLocalRunnerPool())
t.Run("health route is accessible", func(t *testing.T) { t.Run("health route is accessible", func(t *testing.T) {
request, err := http.NewRequest(http.MethodGet, "/api/v1/health", nil) request, err := http.NewRequest(http.MethodGet, "/api/v1/health", nil)

View File

@ -3,7 +3,6 @@ package api
import ( import (
"encoding/json" "encoding/json"
"gitlab.hpi.de/codeocean/codemoon/poseidon/api/dto" "gitlab.hpi.de/codeocean/codemoon/poseidon/api/dto"
"log"
"net/http" "net/http"
) )

View File

@ -8,13 +8,24 @@ import (
"gitlab.hpi.de/codeocean/codemoon/poseidon/api/dto" "gitlab.hpi.de/codeocean/codemoon/poseidon/api/dto"
"gitlab.hpi.de/codeocean/codemoon/poseidon/config" "gitlab.hpi.de/codeocean/codemoon/poseidon/config"
"gitlab.hpi.de/codeocean/codemoon/poseidon/environment" "gitlab.hpi.de/codeocean/codemoon/poseidon/environment"
"gitlab.hpi.de/codeocean/codemoon/poseidon/environment/pool"
"gitlab.hpi.de/codeocean/codemoon/poseidon/runner" "gitlab.hpi.de/codeocean/codemoon/poseidon/runner"
"log"
"net/http" "net/http"
"sync" "sync"
) )
// ProvideRunner tries to respond with the id of a runner var (
executions = make(map[string]map[string]dto.ExecutionRequest)
executionsLock = sync.Mutex{}
)
func allocateExecutionMap(runner runner.Runner) {
executionsLock.Lock()
executions[runner.Id()] = make(map[string]dto.ExecutionRequest)
executionsLock.Unlock()
}
// provideRunner tries to respond with the id of a runner
// This runner is then reserved for future use // This runner is then reserved for future use
func provideRunner(writer http.ResponseWriter, request *http.Request) { func provideRunner(writer http.ResponseWriter, request *http.Request) {
runnerRequest := new(dto.RunnerRequest) runnerRequest := new(dto.RunnerRequest)
@ -31,92 +42,87 @@ func provideRunner(writer http.ResponseWriter, request *http.Request) {
writeInternalServerError(writer, err, dto.ErrorNomadOverload) writeInternalServerError(writer, err, dto.ErrorNomadOverload)
return return
} }
executionsLock.Lock() allocateExecutionMap(runner)
executions[runner.Id] = make(map[string]dto.ExecutionRequest) sendJson(writer, &dto.RunnerResponse{Id: runner.Id()}, http.StatusOK)
executionsLock.Unlock()
sendJson(writer, &dto.RunnerResponse{Id: runner.Id}, http.StatusOK)
} }
var ( // executeCommand takes an ExecutionRequest and stores it for a runner.
executions = make(map[string]map[string]dto.ExecutionRequest) // It returns a url to connect to for a websocket connection to this execution in the corresponding runner.
executionsLock = sync.Mutex{} func executeCommand(router *mux.Router) func(w http.ResponseWriter, r *http.Request) {
) return func(writer http.ResponseWriter, request *http.Request) {
executionRequest := new(dto.ExecutionRequest)
if err := parseRequestBodyJSON(writer, request, executionRequest); err != nil {
return
}
func executeCommand(writer http.ResponseWriter, request *http.Request) { var scheme string
executionRequest := new(dto.ExecutionRequest) if config.Config.Server.TLS {
if err := parseRequestBodyJSON(writer, request, executionRequest); err != nil { scheme = "wss"
return } else {
} scheme = "ws"
}
r, ok := runner.FromContext(request.Context())
if !ok {
log.Fatal("Expected runner in context! Something must be broken ...")
}
var scheme string id, err := uuid.NewRandom()
if config.Config.Server.TLS { if err != nil {
scheme = "wss" log.Printf("Error creating new execution id: %v", err)
} else { writeInternalServerError(writer, err, dto.ErrorUnknown)
scheme = "ws" return
} }
r, ok := runner.FromContext(request.Context())
if !ok {
log.Fatal("Expected runner in context! Something must be broken ...")
}
id, err := uuid.NewRandom() executionsLock.Lock()
if err != nil { runnerExecutions, ok := executions[r.Id()]
log.Printf("Error creating new execution id: %v", err) if !ok {
writeInternalServerError(writer, err, dto.ErrorUnknown) writeNotFound(writer, errors.New("runner has not been provided"))
return return
} }
runnerExecutions[id.String()] = *executionRequest
executionsLock.Unlock()
executionsLock.Lock() path, err := router.Get("runner-websocket").URL("runnerId", r.Id())
runnerExecutions, ok := executions[r.Id] if err != nil {
if !ok { log.Printf("Error creating runner websocket URL %v", err)
writeNotFound(writer, errors.New("runner has not been provided")) writeInternalServerError(writer, err, dto.ErrorUnknown)
return return
}
websocketUrl := fmt.Sprintf("%s://%s%s?executionId=%s", scheme, request.Host, path, id)
sendJson(writer, &dto.WebsocketResponse{WebsocketUrl: websocketUrl}, http.StatusOK)
} }
runnerExecutions[id.String()] = *executionRequest
executionsLock.Unlock()
path, err := router.Get("runner-websocket").URL("runnerId", r.Id)
if err != nil {
log.Printf("Error creating runner websocket URL %v", err)
writeInternalServerError(writer, err, dto.ErrorUnknown)
return
}
websocketUrl := fmt.Sprintf("%s://%s%s?executionId=%s", scheme, request.Host, path, id)
sendJson(writer, &dto.WebsocketResponse{WebsocketUrl: websocketUrl}, http.StatusOK)
} }
func connectToRunner(writer http.ResponseWriter, request *http.Request) { func connectToRunner(writer http.ResponseWriter, request *http.Request) {
// Upgrade the connection to websocket // Todo: Execute the command, upgrade the connection to websocket and handle forwarding
executionId := request.URL.Query()["executionId"] executionId := request.URL.Query()["executionId"]
log.Printf("Websocket for execution %s requested", executionId) log.Printf("Websocket for execution %s requested", executionId)
writer.WriteHeader(http.StatusNotImplemented) writer.WriteHeader(http.StatusNotImplemented)
} }
func findRunnerMiddleware(next http.Handler) http.Handler { // The findRunnerMiddleware looks up the runnerId for routes containing it
return http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) { // and adds the runner to the context of the request.
// Find runner func findRunnerMiddleware(runnerPool pool.RunnerPool) func(handler http.Handler) http.Handler {
runnerId := mux.Vars(request)["runnerId"] return func(next http.Handler) http.Handler {
// TODO: Get runner from runner store using runnerId return http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
env, err := execution_environment.GetExecutionEnvironment(1) // Find runner
if err != nil { runnerId := mux.Vars(request)["runnerId"]
writeNotFound(writer, err) r, ok := runnerPool.GetRunner(runnerId)
return if !ok {
} writer.WriteHeader(http.StatusNotFound)
r, ok := env.Runners[runnerId] return
if !ok { }
writer.WriteHeader(http.StatusNotFound) ctx := runner.NewContext(request.Context(), r)
return requestWithRunner := request.WithContext(ctx)
} next.ServeHTTP(writer, requestWithRunner)
ctx := runner.NewContext(request.Context(), r) })
requestWithRunner := request.WithContext(ctx) }
next.ServeHTTP(writer, requestWithRunner)
})
} }
func registerRunnerRoutes(router *mux.Router) { func registerRunnerRoutes(router *mux.Router, runnerPool pool.RunnerPool) {
router.HandleFunc("", provideRunner).Methods(http.MethodPost) router.HandleFunc("", provideRunner).Methods(http.MethodPost)
runnerRouter := router.PathPrefix("/{runnerId}").Subrouter() runnerRouter := router.PathPrefix("/{runnerId}").Subrouter()
runnerRouter.Use(findRunnerMiddleware) runnerRouter.Use(findRunnerMiddleware(runnerPool))
runnerRouter.HandleFunc("/execute", executeCommand).Methods(http.MethodPost).Name("runner-execute") runnerRouter.HandleFunc("/execute", executeCommand(runnerRouter)).Methods(http.MethodPost).Name("runner-execute")
runnerRouter.HandleFunc("/websocket", connectToRunner).Methods(http.MethodGet).Name("runner-websocket") runnerRouter.HandleFunc("/websocket", connectToRunner).Methods(http.MethodGet).Name("runner-websocket")
} }

150
api/runners_test.go Normal file
View File

@ -0,0 +1,150 @@
package api
import (
"bytes"
"encoding/json"
"github.com/gorilla/mux"
"github.com/stretchr/testify/assert"
"gitlab.hpi.de/codeocean/codemoon/poseidon/api/dto"
"gitlab.hpi.de/codeocean/codemoon/poseidon/environment/pool"
"gitlab.hpi.de/codeocean/codemoon/poseidon/runner"
"io"
"net/http"
"net/http/httptest"
"net/url"
"strings"
"testing"
)
func TestFindRunnerMiddleware(t *testing.T) {
runnerPool := pool.NewLocalRunnerPool()
var capturedRunner runner.Runner
testRunner := runner.NewExerciseRunner("testRunner")
runnerPool.AddRunner(testRunner)
testRunnerIdRoute := func(writer http.ResponseWriter, request *http.Request) {
var ok bool
capturedRunner, ok = runner.FromContext(request.Context())
if ok {
writer.WriteHeader(http.StatusOK)
} else {
writer.WriteHeader(http.StatusInternalServerError)
}
}
router := mux.NewRouter()
router.Use(findRunnerMiddleware(runnerPool))
router.HandleFunc("/test/{runnerId}", testRunnerIdRoute).Name("test-runner-id")
testRunnerRequest := func(t *testing.T, runnerId string) *http.Request {
path, err := router.Get("test-runner-id").URL("runnerId", runnerId)
if err != nil {
t.Fatal(err)
}
request, err := http.NewRequest(
http.MethodPost, path.String(), nil)
if err != nil {
t.Fatal(err)
}
return request
}
t.Run("sets runner in context if runner exists", func(t *testing.T) {
capturedRunner = nil
recorder := httptest.NewRecorder()
router.ServeHTTP(recorder, testRunnerRequest(t, testRunner.Id()))
assert.Equal(t, http.StatusOK, recorder.Code)
assert.Equal(t, testRunner, capturedRunner)
})
t.Run("returns 404 if runner does not exist", func(t *testing.T) {
recorder := httptest.NewRecorder()
router.ServeHTTP(recorder, testRunnerRequest(t, "some-invalid-runner-id"))
assert.Equal(t, http.StatusNotFound, recorder.Code)
})
}
func TestExecuteRoute(t *testing.T) {
runnerPool := pool.NewLocalRunnerPool()
router := NewRouter(runnerPool)
testRunner := runner.NewExerciseRunner("testRunner")
runnerPool.AddRunner(testRunner)
allocateExecutionMap(testRunner)
path, err := router.Get("runner-execute").URL("runnerId", testRunner.Id())
if err != nil {
t.Fatal("Could not construct execute url")
}
t.Run("valid request", func(t *testing.T) {
recorder := httptest.NewRecorder()
executionRequest := dto.ExecutionRequest{
Command: "command",
TimeLimit: 10,
Environment: nil,
}
body, err := json.Marshal(executionRequest)
if err != nil {
t.Fatal(err)
}
request, err := http.NewRequest(http.MethodPost, path.String(), bytes.NewReader(body))
if err != nil {
t.Fatal(err)
}
router.ServeHTTP(recorder, request)
responseBody, err := io.ReadAll(recorder.Result().Body)
if err != nil {
t.Fatal(err)
}
var websocketResponse dto.WebsocketResponse
err = json.Unmarshal(responseBody, &websocketResponse)
if err != nil {
t.Fatal(err)
}
t.Run("returns 200", func(t *testing.T) {
assert.Equal(t, http.StatusOK, recorder.Code)
})
t.Run("creates an execution request for the runner", func(t *testing.T) {
url, err := url.Parse(websocketResponse.WebsocketUrl)
if err != nil {
t.Fatal(err)
}
executionId := url.Query().Get("executionId")
assert.Equal(t, executionRequest, executions[testRunner.Id()][executionId])
})
})
t.Run("invalid request", func(t *testing.T) {
recorder := httptest.NewRecorder()
body := ""
request, err := http.NewRequest(http.MethodPost, path.String(), strings.NewReader(body))
if err != nil {
t.Fatal(err)
}
router.ServeHTTP(recorder, request)
_, err = io.ReadAll(recorder.Result().Body)
if err != nil {
t.Fatal(err)
}
t.Run("returns 400", func(t *testing.T) {
assert.Equal(t, http.StatusBadRequest, recorder.Code)
})
})
}

View File

@ -16,7 +16,7 @@ func TestProvideRunnerRoute(t *testing.T) {
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, http.StatusOK, resp.StatusCode, "The response code should be ok") assert.Equal(t, http.StatusOK, resp.StatusCode, "The response code should be ok")
runnerResponse := new(dto.ResponseRunner) runnerResponse := new(dto.RunnerResponse)
err = json.NewDecoder(resp.Body).Decode(runnerResponse) err = json.NewDecoder(resp.Body).Decode(runnerResponse)
assert.NoError(t, err) assert.NoError(t, err)

24
go.sum
View File

@ -3,23 +3,40 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/docker/go-units v0.3.3 h1:Xk8S3Xj5sLGlG5g67hJmYMmUgXv5N4PhkjJHHqrwnTk= github.com/docker/go-units v0.3.3 h1:Xk8S3Xj5sLGlG5g67hJmYMmUgXv5N4PhkjJHHqrwnTk=
github.com/docker/go-units v0.3.3 h1:Xk8S3Xj5sLGlG5g67hJmYMmUgXv5N4PhkjJHHqrwnTk=
github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs= github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs=
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/hashicorp/cronexpr v1.1.0/go.mod h1:P4wA0KBl9C5q2hABiMO7cp6jcIg96CDh1Efb3g1PWA4=
github.com/hashicorp/cronexpr v1.1.0/go.mod h1:P4wA0KBl9C5q2hABiMO7cp6jcIg96CDh1Efb3g1PWA4= github.com/hashicorp/cronexpr v1.1.0/go.mod h1:P4wA0KBl9C5q2hABiMO7cp6jcIg96CDh1Efb3g1PWA4=
github.com/hashicorp/cronexpr v1.1.1 h1:NJZDd87hGXjoZBdvyCF9mX4DCq5Wy7+A/w+A7q0wn6c= github.com/hashicorp/cronexpr v1.1.1 h1:NJZDd87hGXjoZBdvyCF9mX4DCq5Wy7+A/w+A7q0wn6c=
github.com/hashicorp/cronexpr v1.1.1 h1:NJZDd87hGXjoZBdvyCF9mX4DCq5Wy7+A/w+A7q0wn6c=
github.com/hashicorp/cronexpr v1.1.1/go.mod h1:P4wA0KBl9C5q2hABiMO7cp6jcIg96CDh1Efb3g1PWA4=
github.com/hashicorp/cronexpr v1.1.1/go.mod h1:P4wA0KBl9C5q2hABiMO7cp6jcIg96CDh1Efb3g1PWA4= github.com/hashicorp/cronexpr v1.1.1/go.mod h1:P4wA0KBl9C5q2hABiMO7cp6jcIg96CDh1Efb3g1PWA4=
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc=
github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc= github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc=
github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8=
github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8=
github.com/hashicorp/nomad/api v0.0.0-20210430020956-28b8767b278f h1:XaaK4F+pOV2ZYk3aCIDAZThm3voKD6nyOUNeUY3yHgY= github.com/hashicorp/nomad/api v0.0.0-20210430020956-28b8767b278f h1:XaaK4F+pOV2ZYk3aCIDAZThm3voKD6nyOUNeUY3yHgY=
github.com/hashicorp/nomad/api v0.0.0-20210430020956-28b8767b278f h1:XaaK4F+pOV2ZYk3aCIDAZThm3voKD6nyOUNeUY3yHgY=
github.com/hashicorp/nomad/api v0.0.0-20210430020956-28b8767b278f/go.mod h1:vYHP9jMXk4/T2qNUbWlQ1OHCA1hHLil3nvqSmz8mtgc=
github.com/hashicorp/nomad/api v0.0.0-20210430020956-28b8767b278f/go.mod h1:vYHP9jMXk4/T2qNUbWlQ1OHCA1hHLil3nvqSmz8mtgc= github.com/hashicorp/nomad/api v0.0.0-20210430020956-28b8767b278f/go.mod h1:vYHP9jMXk4/T2qNUbWlQ1OHCA1hHLil3nvqSmz8mtgc=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
@ -32,7 +49,10 @@ github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrk
github.com/mitchellh/go-testing-interface v1.0.0 h1:fzU/JVNcaqHQEcVFAKeR41fkiLdIPrefOvVG1VZ96U0= github.com/mitchellh/go-testing-interface v1.0.0 h1:fzU/JVNcaqHQEcVFAKeR41fkiLdIPrefOvVG1VZ96U0=
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
github.com/mitchellh/mapstructure v1.3.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.3.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/mitchellh/mapstructure v1.3.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag= github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag=
github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag=
github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
@ -49,10 +69,14 @@ golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210426230700-d19ff857e887 h1:dXfMednGJh/SUUFjTLsWJz3P+TQt9qnR11GgeI3vWKs= golang.org/x/sys v0.0.0-20210426230700-d19ff857e887 h1:dXfMednGJh/SUUFjTLsWJz3P+TQt9qnR11GgeI3vWKs=
golang.org/x/sys v0.0.0-20210426230700-d19ff857e887/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210426230700-d19ff857e887/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

14
main.go
View File

@ -38,13 +38,13 @@ func runServer(server *http.Server) {
} }
} }
func initServer() *http.Server { func initServer(runnerPool pool.RunnerPool) *http.Server {
return &http.Server{ return &http.Server{
Addr: config.Config.PoseidonAPIURL().Host, Addr: config.Config.PoseidonAPIURL().Host,
WriteTimeout: time.Second * 15, WriteTimeout: time.Second * 15,
ReadTimeout: time.Second * 15, ReadTimeout: time.Second * 15,
IdleTimeout: time.Second * 60, IdleTimeout: time.Second * 60,
Handler: api.NewRouter(), Handler: api.NewRouter(runnerPool),
} }
} }
@ -67,17 +67,19 @@ func main() {
log.WithError(err).Warn("Could not initialize configuration") log.WithError(err).Warn("Could not initialize configuration")
} }
logging.InitializeLogging(config.Config.Logger.Level) logging.InitializeLogging(config.Config.Logger.Level)
server := initServer()
log.WithField("address", server.Addr).Info("Starting server")
// API initialization // API initialization
nomadAPIClient, err := nomad.New(config.Config.NomadAPIURL()) nomadAPIClient, err := nomad.New(config.Config.NomadAPIURL())
if err != nil { if err != nil {
log.WithError(err).WithField("nomad url", config.Config.NomadAPIURL()).Fatal("Error parsing the nomad url") log.WithError(err).WithField("nomad url", config.Config.NomadAPIURL()).Fatal("Error parsing the nomad url")
} }
// ToDo: Move to create execution environment // ToDo: Move to create execution environment
runnersPool := pool.NewLocalRunnerPool() runnerPool := pool.NewLocalRunnerPool()
environment.DebugInit(runnersPool, nomadAPIClient) environment.DebugInit(runnerPool, nomadAPIClient)
server := initServer(runnerPool)
log.WithField("address", server.Addr).Info("Starting server")
go runServer(server) go runServer(server)
shutdownOnOSSignal(server) shutdownOnOSSignal(server)

View File

@ -7,6 +7,7 @@ import (
) )
type Status string type Status string
type ContextKey string
const ( const (
StatusReady Status = "ready" StatusReady Status = "ready"
@ -15,7 +16,7 @@ const (
StatusFinished Status = "finished" StatusFinished Status = "finished"
// runnerContextKey is the key used to store runners in context.Context // runnerContextKey is the key used to store runners in context.Context
runnerContextKey = "runner" runnerContextKey ContextKey = "runner"
) )
type Runner interface { type Runner interface {
@ -74,11 +75,11 @@ func (r *ExerciseRunner) Id() string {
return r.id return r.id
} }
func NewContext(ctx context.Context, runner *Runner) context.Context { func NewContext(ctx context.Context, runner Runner) context.Context {
return context.WithValue(ctx, runnerContextKey, runner) return context.WithValue(ctx, runnerContextKey, runner)
} }
func FromContext(ctx context.Context) (*Runner, bool) { func FromContext(ctx context.Context) (Runner, bool) {
runner, ok := ctx.Value(runnerContextKey).(*Runner) runner, ok := ctx.Value(runnerContextKey).(Runner)
return runner, ok return runner, ok
} }

View File

@ -1,6 +1,7 @@
package runner package runner
import ( import (
"context"
"encoding/json" "encoding/json"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"testing" "testing"
@ -30,3 +31,29 @@ func TestMarshalRunner(t *testing.T) {
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, "{\"runnerId\":\"42\",\"status\":\"ready\"}", string(marshal)) assert.Equal(t, "{\"runnerId\":\"42\",\"status\":\"ready\"}", string(marshal))
} }
func TestNewContextReturnsNewContextWithRunner(t *testing.T) {
runner := NewExerciseRunner("testRunner")
ctx := context.Background()
newCtx := NewContext(ctx, runner)
storedRunner := newCtx.Value(runnerContextKey).(Runner)
assert.NotEqual(t, ctx, newCtx)
assert.Equal(t, runner, storedRunner)
}
func TestFromContextReturnsRunner(t *testing.T) {
runner := NewExerciseRunner("testRunner")
ctx := NewContext(context.Background(), runner)
storedRunner, ok := FromContext(ctx)
assert.True(t, ok)
assert.Equal(t, runner, storedRunner)
}
func TestFromContextReturnsIsNotOkWhenContextHasNoRunner(t *testing.T) {
ctx := context.Background()
_, ok := FromContext(ctx)
assert.False(t, ok)
}