From 612bc55bddbfef67cb136fa7ae8a29a6548bcc8a Mon Sep 17 00:00:00 2001 From: Konrad Hanff Date: Fri, 30 Apr 2021 16:23:00 +0200 Subject: [PATCH] Add tests for runners execute route Co-authored-by: Tobias Kantusch --- api/api.go | 12 ++-- api/api_test.go | 5 +- api/helpers.go | 1 - api/runners.go | 146 ++++++++++++++++++++-------------------- api/runners_test.go | 150 ++++++++++++++++++++++++++++++++++++++++++ e2e/runners_test.go | 2 +- go.sum | 24 +++++++ main.go | 14 ++-- runner/runner.go | 9 +-- runner/runner_test.go | 27 ++++++++ 10 files changed, 300 insertions(+), 90 deletions(-) create mode 100644 api/runners_test.go diff --git a/api/api.go b/api/api.go index 2a8e240..3b5d6bf 100644 --- a/api/api.go +++ b/api/api.go @@ -3,7 +3,7 @@ package api import ( "github.com/gorilla/mux" "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" "net/http" ) @@ -21,18 +21,18 @@ const ( // 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. // when extracting path parameters. -func NewRouter() http.Handler { +func NewRouter(runnerPool pool.RunnerPool) *mux.Router { router := mux.NewRouter() - router.Use(logging.HTTPLoggingMiddleware) // this can later be restricted to a specific host with // `router.Host(...)` and to HTTPS with `router.Schemes("https")` - newRouterV1(router) + router = newRouterV1(router, runnerPool) + router.Use(logging.HTTPLoggingMiddleware) return router } // newRouterV1 returns a sub-router containing the routes of version // 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.HandleFunc(RouteHealth, Health).Methods(http.MethodGet) @@ -42,7 +42,7 @@ func newRouterV1(router *mux.Router) *mux.Router { v1 = v1.PathPrefix("").Subrouter() v1.Use(auth.HTTPAuthenticationMiddleware) } - registerRunnerRoutes(v1.PathPrefix(RouteRunners).Subrouter()) + registerRunnerRoutes(v1.PathPrefix(RouteRunners).Subrouter(), runnerPool) return v1 } diff --git a/api/api_test.go b/api/api_test.go index ea2fe2b..a094ee6 100644 --- a/api/api_test.go +++ b/api/api_test.go @@ -4,6 +4,7 @@ import ( "github.com/gorilla/mux" "github.com/stretchr/testify/assert" "gitlab.hpi.de/codeocean/codemoon/poseidon/config" + "gitlab.hpi.de/codeocean/codemoon/poseidon/environment/pool" "net/http" "net/http/httptest" "testing" @@ -16,7 +17,7 @@ func mockHTTPHandler(writer http.ResponseWriter, _ *http.Request) { func TestNewRouterV1WithAuthenticationDisabled(t *testing.T) { config.Config.Server.Token = "" router := mux.NewRouter() - v1 := newRouterV1(router) + v1 := newRouterV1(router, pool.NewLocalRunnerPool()) t.Run("health route is accessible", func(t *testing.T) { request, err := http.NewRequest(http.MethodGet, "/api/v1/health", nil) @@ -43,7 +44,7 @@ func TestNewRouterV1WithAuthenticationDisabled(t *testing.T) { func TestNewRouterV1WithAuthenticationEnabled(t *testing.T) { config.Config.Server.Token = "TestToken" router := mux.NewRouter() - v1 := newRouterV1(router) + v1 := newRouterV1(router, pool.NewLocalRunnerPool()) t.Run("health route is accessible", func(t *testing.T) { request, err := http.NewRequest(http.MethodGet, "/api/v1/health", nil) diff --git a/api/helpers.go b/api/helpers.go index f2b818a..be92928 100644 --- a/api/helpers.go +++ b/api/helpers.go @@ -3,7 +3,6 @@ package api import ( "encoding/json" "gitlab.hpi.de/codeocean/codemoon/poseidon/api/dto" - "log" "net/http" ) diff --git a/api/runners.go b/api/runners.go index d8a7c00..6c62bbd 100644 --- a/api/runners.go +++ b/api/runners.go @@ -8,13 +8,24 @@ import ( "gitlab.hpi.de/codeocean/codemoon/poseidon/api/dto" "gitlab.hpi.de/codeocean/codemoon/poseidon/config" "gitlab.hpi.de/codeocean/codemoon/poseidon/environment" + "gitlab.hpi.de/codeocean/codemoon/poseidon/environment/pool" "gitlab.hpi.de/codeocean/codemoon/poseidon/runner" - "log" "net/http" "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 func provideRunner(writer http.ResponseWriter, request *http.Request) { runnerRequest := new(dto.RunnerRequest) @@ -31,92 +42,87 @@ func provideRunner(writer http.ResponseWriter, request *http.Request) { writeInternalServerError(writer, err, dto.ErrorNomadOverload) return } - executionsLock.Lock() - executions[runner.Id] = make(map[string]dto.ExecutionRequest) - executionsLock.Unlock() - sendJson(writer, &dto.RunnerResponse{Id: runner.Id}, http.StatusOK) + allocateExecutionMap(runner) + sendJson(writer, &dto.RunnerResponse{Id: runner.Id()}, http.StatusOK) } -var ( - executions = make(map[string]map[string]dto.ExecutionRequest) - executionsLock = sync.Mutex{} -) +// executeCommand takes an ExecutionRequest and stores it for a runner. +// It returns a url to connect to for a websocket connection to this execution in the corresponding runner. +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) { - executionRequest := new(dto.ExecutionRequest) - if err := parseRequestBodyJSON(writer, request, executionRequest); err != nil { - return - } + var scheme string + if config.Config.Server.TLS { + scheme = "wss" + } else { + scheme = "ws" + } + r, ok := runner.FromContext(request.Context()) + if !ok { + log.Fatal("Expected runner in context! Something must be broken ...") + } - var scheme string - if config.Config.Server.TLS { - scheme = "wss" - } else { - scheme = "ws" - } - r, ok := runner.FromContext(request.Context()) - if !ok { - log.Fatal("Expected runner in context! Something must be broken ...") - } + id, err := uuid.NewRandom() + if err != nil { + log.Printf("Error creating new execution id: %v", err) + writeInternalServerError(writer, err, dto.ErrorUnknown) + return + } - id, err := uuid.NewRandom() - if err != nil { - log.Printf("Error creating new execution id: %v", err) - writeInternalServerError(writer, err, dto.ErrorUnknown) - return - } + executionsLock.Lock() + runnerExecutions, ok := executions[r.Id()] + if !ok { + writeNotFound(writer, errors.New("runner has not been provided")) + return + } + runnerExecutions[id.String()] = *executionRequest + executionsLock.Unlock() - executionsLock.Lock() - runnerExecutions, ok := executions[r.Id] - if !ok { - writeNotFound(writer, errors.New("runner has not been provided")) - return + 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) } - 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) { - // Upgrade the connection to websocket + // Todo: Execute the command, upgrade the connection to websocket and handle forwarding executionId := request.URL.Query()["executionId"] log.Printf("Websocket for execution %s requested", executionId) writer.WriteHeader(http.StatusNotImplemented) } -func findRunnerMiddleware(next http.Handler) http.Handler { - return http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) { - // Find runner - runnerId := mux.Vars(request)["runnerId"] - // TODO: Get runner from runner store using runnerId - env, err := execution_environment.GetExecutionEnvironment(1) - if err != nil { - writeNotFound(writer, err) - return - } - r, ok := env.Runners[runnerId] - if !ok { - writer.WriteHeader(http.StatusNotFound) - return - } - ctx := runner.NewContext(request.Context(), r) - requestWithRunner := request.WithContext(ctx) - next.ServeHTTP(writer, requestWithRunner) - }) +// The findRunnerMiddleware looks up the runnerId for routes containing it +// and adds the runner to the context of the request. +func findRunnerMiddleware(runnerPool pool.RunnerPool) func(handler http.Handler) http.Handler { + return func(next http.Handler) http.Handler { + return http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) { + // Find runner + runnerId := mux.Vars(request)["runnerId"] + r, ok := runnerPool.GetRunner(runnerId) + if !ok { + writer.WriteHeader(http.StatusNotFound) + return + } + 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) runnerRouter := router.PathPrefix("/{runnerId}").Subrouter() - runnerRouter.Use(findRunnerMiddleware) - runnerRouter.HandleFunc("/execute", executeCommand).Methods(http.MethodPost).Name("runner-execute") + runnerRouter.Use(findRunnerMiddleware(runnerPool)) + runnerRouter.HandleFunc("/execute", executeCommand(runnerRouter)).Methods(http.MethodPost).Name("runner-execute") runnerRouter.HandleFunc("/websocket", connectToRunner).Methods(http.MethodGet).Name("runner-websocket") } diff --git a/api/runners_test.go b/api/runners_test.go new file mode 100644 index 0000000..f001bed --- /dev/null +++ b/api/runners_test.go @@ -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) + }) + + }) +} diff --git a/e2e/runners_test.go b/e2e/runners_test.go index c131213..5d6b570 100644 --- a/e2e/runners_test.go +++ b/e2e/runners_test.go @@ -16,7 +16,7 @@ func TestProvideRunnerRoute(t *testing.T) { assert.NoError(t, err) 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) assert.NoError(t, err) diff --git a/go.sum b/go.sum index 0a413f2..1509de4 100644 --- a/go.sum +++ b/go.sum @@ -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/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/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/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/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.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/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/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.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-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/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/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= 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/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.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/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 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/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 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/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.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/main.go b/main.go index 84ece86..ab278c2 100644 --- a/main.go +++ b/main.go @@ -38,13 +38,13 @@ func runServer(server *http.Server) { } } -func initServer() *http.Server { +func initServer(runnerPool pool.RunnerPool) *http.Server { return &http.Server{ Addr: config.Config.PoseidonAPIURL().Host, WriteTimeout: time.Second * 15, ReadTimeout: time.Second * 15, 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") } logging.InitializeLogging(config.Config.Logger.Level) - server := initServer() - log.WithField("address", server.Addr).Info("Starting server") // API initialization nomadAPIClient, err := nomad.New(config.Config.NomadAPIURL()) if err != nil { log.WithError(err).WithField("nomad url", config.Config.NomadAPIURL()).Fatal("Error parsing the nomad url") } + // ToDo: Move to create execution environment - runnersPool := pool.NewLocalRunnerPool() - environment.DebugInit(runnersPool, nomadAPIClient) + runnerPool := pool.NewLocalRunnerPool() + environment.DebugInit(runnerPool, nomadAPIClient) + + server := initServer(runnerPool) + log.WithField("address", server.Addr).Info("Starting server") go runServer(server) shutdownOnOSSignal(server) diff --git a/runner/runner.go b/runner/runner.go index 295e34f..a35b2ec 100644 --- a/runner/runner.go +++ b/runner/runner.go @@ -7,6 +7,7 @@ import ( ) type Status string +type ContextKey string const ( StatusReady Status = "ready" @@ -15,7 +16,7 @@ const ( StatusFinished Status = "finished" // runnerContextKey is the key used to store runners in context.Context - runnerContextKey = "runner" + runnerContextKey ContextKey = "runner" ) type Runner interface { @@ -74,11 +75,11 @@ func (r *ExerciseRunner) Id() string { 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) } -func FromContext(ctx context.Context) (*Runner, bool) { - runner, ok := ctx.Value(runnerContextKey).(*Runner) +func FromContext(ctx context.Context) (Runner, bool) { + runner, ok := ctx.Value(runnerContextKey).(Runner) return runner, ok } diff --git a/runner/runner_test.go b/runner/runner_test.go index 4433a28..4330b07 100644 --- a/runner/runner_test.go +++ b/runner/runner_test.go @@ -1,6 +1,7 @@ package runner import ( + "context" "encoding/json" "github.com/stretchr/testify/assert" "testing" @@ -30,3 +31,29 @@ func TestMarshalRunner(t *testing.T) { assert.NoError(t, err) 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) +}