Add destroy runner route

This commit is contained in:
Jan-Eric Hellenberg
2021-05-05 09:07:23 +02:00
parent e45cd92557
commit ba51956ec3
7 changed files with 144 additions and 16 deletions

View File

@ -5,6 +5,7 @@ import (
"gitlab.hpi.de/codeocean/codemoon/poseidon/api/auth"
"gitlab.hpi.de/codeocean/codemoon/poseidon/environment"
"gitlab.hpi.de/codeocean/codemoon/poseidon/logging"
"gitlab.hpi.de/codeocean/codemoon/poseidon/nomad"
"net/http"
)
@ -21,18 +22,17 @@ 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(runnerPool environment.RunnerPool) *mux.Router {
func NewRouter(apiClient nomad.ExecutorApi, runnerPool environment.RunnerPool) *mux.Router {
router := mux.NewRouter()
// this can later be restricted to a specific host with
// `router.Host(...)` and to HTTPS with `router.Schemes("https")`
router = newRouterV1(router, runnerPool)
router = newRouterV1(router, apiClient, 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, runnerPool environment.RunnerPool) *mux.Router {
// newRouterV1 returns a sub-router containing the routes of version 1 of our API.
func newRouterV1(router *mux.Router, apiClient nomad.ExecutorApi, runnerPool environment.RunnerPool) *mux.Router {
v1 := router.PathPrefix(RouteBase).Subrouter()
v1.HandleFunc(RouteHealth, Health).Methods(http.MethodGet)
@ -42,7 +42,7 @@ func newRouterV1(router *mux.Router, runnerPool environment.RunnerPool) *mux.Rou
v1 = v1.PathPrefix("").Subrouter()
v1.Use(auth.HTTPAuthenticationMiddleware)
}
registerRunnerRoutes(v1.PathPrefix(RouteRunners).Subrouter(), runnerPool)
registerRunnerRoutes(v1.PathPrefix(RouteRunners).Subrouter(), apiClient, runnerPool)
return v1
}

View File

@ -17,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, environment.NewLocalRunnerPool())
v1 := newRouterV1(router, nil, environment.NewLocalRunnerPool())
t.Run("health route is accessible", func(t *testing.T) {
request, err := http.NewRequest(http.MethodGet, "/api/v1/health", nil)
@ -44,7 +44,7 @@ func TestNewRouterV1WithAuthenticationDisabled(t *testing.T) {
func TestNewRouterV1WithAuthenticationEnabled(t *testing.T) {
config.Config.Server.Token = "TestToken"
router := mux.NewRouter()
v1 := newRouterV1(router, environment.NewLocalRunnerPool())
v1 := newRouterV1(router, nil, environment.NewLocalRunnerPool())
t.Run("health route is accessible", func(t *testing.T) {
request, err := http.NewRequest(http.MethodGet, "/api/v1/health", nil)

View File

@ -7,6 +7,7 @@ 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/nomad"
"gitlab.hpi.de/codeocean/codemoon/poseidon/runner"
"net/http"
"net/url"
@ -15,6 +16,7 @@ import (
const (
ExecutePath = "/execute"
WebsocketPath = "/websocket"
DeleteRoute = "deleteRunner"
RunnerIdKey = "runnerId"
ExecutionIdKey = "executionId"
)
@ -111,10 +113,27 @@ func findRunnerMiddleware(runnerPool environment.RunnerPool) func(handler http.H
}
}
func registerRunnerRoutes(router *mux.Router, runnerPool environment.RunnerPool) {
func deleteRunner(apiClient nomad.ExecutorApi, runnerPool environment.RunnerPool) func(writer http.ResponseWriter, request *http.Request) {
return func(writer http.ResponseWriter, request *http.Request) {
targetRunner, _ := runner.FromContext(request.Context())
err := apiClient.DeleteRunner(targetRunner.Id())
if err != nil {
writeInternalServerError(writer, err, dto.ErrorNomadInternalServerError)
return
}
runnerPool.Delete(targetRunner.Id())
writer.WriteHeader(http.StatusNoContent)
}
}
func registerRunnerRoutes(router *mux.Router, apiClient nomad.ExecutorApi, runnerPool environment.RunnerPool) {
router.HandleFunc("", provideRunner).Methods(http.MethodPost)
runnerRouter := router.PathPrefix(fmt.Sprintf("/{%s}", RunnerIdKey)).Subrouter()
runnerRouter.Use(findRunnerMiddleware(runnerPool))
runnerRouter.HandleFunc(ExecutePath, executeCommand(runnerRouter)).Methods(http.MethodPost).Name(ExecutePath)
runnerRouter.HandleFunc(WebsocketPath, connectToRunner).Methods(http.MethodGet).Name(WebsocketPath)
runnerRouter.HandleFunc("", deleteRunner(apiClient, runnerPool)).Methods(http.MethodDelete).Name(DeleteRoute)
}

View File

@ -3,11 +3,15 @@ package api
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"github.com/gorilla/mux"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/suite"
"gitlab.hpi.de/codeocean/codemoon/poseidon/api/dto"
"gitlab.hpi.de/codeocean/codemoon/poseidon/environment"
"gitlab.hpi.de/codeocean/codemoon/poseidon/mocks"
"gitlab.hpi.de/codeocean/codemoon/poseidon/runner"
"net/http"
"net/http/httptest"
@ -67,7 +71,7 @@ func TestFindRunnerMiddleware(t *testing.T) {
func TestExecuteRoute(t *testing.T) {
runnerPool := environment.NewLocalRunnerPool()
router := NewRouter(runnerPool)
router := NewRouter(nil, runnerPool)
testRunner := runner.NewExerciseRunner("testRunner")
runnerPool.Add(testRunner)
@ -127,3 +131,89 @@ func TestExecuteRoute(t *testing.T) {
assert.Equal(t, http.StatusBadRequest, recorder.Code)
})
}
func TestDeleteRunnerRouteTestSuite(t *testing.T) {
suite.Run(t, new(DeleteRunnerRouteTestSuite))
}
type DeleteRunnerRouteTestSuite struct {
suite.Suite
runnerPool environment.RunnerPool
apiClient *mocks.ExecutorApi
router *mux.Router
testRunner runner.Runner
path string
}
func (suite *DeleteRunnerRouteTestSuite) SetupTest() {
suite.runnerPool = environment.NewLocalRunnerPool()
suite.apiClient = &mocks.ExecutorApi{}
suite.router = NewRouter(suite.apiClient, suite.runnerPool)
suite.testRunner = runner.NewExerciseRunner("testRunner")
suite.runnerPool.Add(suite.testRunner)
var err error
runnerUrl, err := suite.router.Get(DeleteRoute).URL(RunnerIdKey, suite.testRunner.Id())
if err != nil {
suite.T().Fatal(err)
}
suite.path = runnerUrl.String()
}
func (suite *DeleteRunnerRouteTestSuite) TestValidRequestReturnsNoContent() {
suite.apiClient.On("DeleteRunner", mock.AnythingOfType("string")).Return(nil)
recorder := httptest.NewRecorder()
request, err := http.NewRequest(http.MethodDelete, suite.path, nil)
if err != nil {
suite.T().Fatal(err)
}
suite.router.ServeHTTP(recorder, request)
suite.Equal(http.StatusNoContent, recorder.Code)
suite.Run("runner is deleted on nomad", func() {
suite.apiClient.AssertCalled(suite.T(), "DeleteRunner", suite.testRunner.Id())
})
suite.Run("runner is deleted from runnerPool", func() {
returnedRunner, ok := suite.runnerPool.Get(suite.testRunner.Id())
suite.Nil(returnedRunner)
suite.False(ok)
})
}
func (suite *DeleteRunnerRouteTestSuite) TestReturnInternalServerErrorWhenApiCallToNomadFailed() {
suite.apiClient.On("DeleteRunner", mock.AnythingOfType("string")).Return(errors.New("API call failed"))
recorder := httptest.NewRecorder()
request, err := http.NewRequest(http.MethodDelete, suite.path, nil)
if err != nil {
suite.T().Fatal(err)
}
suite.router.ServeHTTP(recorder, request)
suite.Equal(http.StatusInternalServerError, recorder.Code)
}
func (suite *DeleteRunnerRouteTestSuite) TestDeleteInvalidRunnerIdReturnsNotFound() {
var err error
runnersUrl, err := suite.router.Get(DeleteRoute).URL(RunnerIdKey, "1nv4l1dID")
if err != nil {
suite.T().Fatal(err)
}
suite.path = runnersUrl.String()
recorder := httptest.NewRecorder()
request, err := http.NewRequest(http.MethodDelete, suite.path, nil)
if err != nil {
suite.T().Fatal(err)
}
suite.router.ServeHTTP(recorder, request)
suite.Equal(http.StatusNotFound, recorder.Code)
}